@56os/search-fields 0.0.0-stage → 0.1.1
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 +66 -2
- package/package.json +36 -4
- package/src/basicSearchConfig.ts +97 -0
- package/src/buildAdvancedQueryParentData.ts +41 -0
- package/src/components/BasicSearchFields.vue +175 -0
- package/src/index.ts +26 -0
- package/src/setup.ts +26 -0
- package/src/shims-vue.d.ts +5 -0
- package/src/types.ts +79 -0
- package/src/useSearchFieldSchema.ts +131 -0
package/README.md
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @56os/search-fields
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
列表页检索字段统一定义共享包:类型、用户排序读写、`useSearchFieldSchema`、`BasicSearchFields`、`buildAdvancedQueryParentData`。
|
|
4
|
+
|
|
5
|
+
> 不进包:各页 `searchFieldSchema`、业务接口、页内特殊插槽逻辑。
|
|
6
|
+
> 高级查询抽屉仍由主包 `Advancedquery` 维护。
|
|
7
|
+
|
|
8
|
+
## 安装
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @56os/search-fields
|
|
12
|
+
# 或
|
|
13
|
+
npm install @56os/search-fields
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
本地联调源码(可选):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @56os/search-fields@file:../56os-search-fields
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 接入(子应用入口必须注入 API)
|
|
23
|
+
|
|
24
|
+
包内**不**绑定某一子包的 `@/api`,需在应用启动时注入 `user-table-config`:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { setupSearchFields } from '@56os/search-fields';
|
|
28
|
+
import { getConfigList, saveConfig } from '@/api/chooseColumn/chooseColumn.js';
|
|
29
|
+
|
|
30
|
+
setupSearchFields({
|
|
31
|
+
userTableConfigApi: {
|
|
32
|
+
getConfigList,
|
|
33
|
+
saveConfig
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 页面用法
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import {
|
|
42
|
+
useSearchFieldSchema,
|
|
43
|
+
BasicSearchFields,
|
|
44
|
+
buildAdvancedQueryParentData
|
|
45
|
+
} from '@56os/search-fields';
|
|
46
|
+
import type { SearchFieldSchemaItem } from '@56os/search-fields';
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<BasicSearchFields
|
|
51
|
+
:model="queryParams"
|
|
52
|
+
:fields="visibleBasicFields"
|
|
53
|
+
:show-advanced="showAdvancedQuery"
|
|
54
|
+
@search="handleSearch"
|
|
55
|
+
@reset="handleReset"
|
|
56
|
+
@advanced="openAdvancedQuery"
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- 配置区标签 / 高级查询:全部可查字段(同序)
|
|
61
|
+
- 页头:`orderedFields.slice(0, basicVisibleCount)`,默认前 4 个
|
|
62
|
+
- **高级查询入口**:`showAdvancedQuery = orderedFields.length > basicVisibleCount`;字段全在页头(如仅 3 个条件)时隐藏「高级查询」
|
|
63
|
+
- 排序落库:宿主 `Advancedquery` 负责;业务页只收 `onBasicOrderChange`
|
|
64
|
+
|
|
65
|
+
## 版本
|
|
66
|
+
|
|
67
|
+
遵循 semver。当前 `0.1.1`:新增 `showAdvancedQuery` / `BasicSearchFields.showAdvanced`(字段全在页头时隐藏高级查询)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@56os/search-fields",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "56os 列表检索字段统一定义:schema / 用户排序 / 页头 BasicSearchFields / 高级查询 parentdata 构建",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"module": "./src/index.ts",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"import": "./src/index.ts",
|
|
13
|
+
"default": "./src/index.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": [
|
|
21
|
+
"**/*.vue",
|
|
22
|
+
"**/*.css"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"element-plus": "^2.7.0",
|
|
26
|
+
"vue": "^3.4.0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"56os",
|
|
30
|
+
"search-fields",
|
|
31
|
+
"vue3",
|
|
32
|
+
"element-plus"
|
|
33
|
+
],
|
|
34
|
+
"license": "UNLICENSED",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getSearchFieldsUserTableConfigApi } from './setup';
|
|
2
|
+
import type { SearchFieldsUserTableConfigApi } from './types';
|
|
3
|
+
|
|
4
|
+
/** 与表头筛选隔离的 url 后缀(禁止手拼 #) */
|
|
5
|
+
export const BASIC_SEARCH_CONFIG_SUFFIX = '__basicSearch';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 生成基础检索排序配置的 url
|
|
9
|
+
* @example toBasicSearchConfigUrl('/EnterpriseInformation') => '/EnterpriseInformation__basicSearch'
|
|
10
|
+
*/
|
|
11
|
+
export function toBasicSearchConfigUrl(pageUrl: string): string {
|
|
12
|
+
const url = String(pageUrl || '').trim();
|
|
13
|
+
if (!url) {
|
|
14
|
+
return url;
|
|
15
|
+
}
|
|
16
|
+
if (url.endsWith(BASIC_SEARCH_CONFIG_SUFFIX)) {
|
|
17
|
+
return url;
|
|
18
|
+
}
|
|
19
|
+
return `${url}${BASIC_SEARCH_CONFIG_SUFFIX}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const splitCsv = (value?: string | null): string[] =>
|
|
23
|
+
String(value || '')
|
|
24
|
+
.split(',')
|
|
25
|
+
.map((item) => item.trim())
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 合并用户 sortconfig 与 schema 默认可查字段顺序。
|
|
30
|
+
* - 配置中未知 key 丢弃
|
|
31
|
+
* - schema 新增且配置中没有的 key 追加到末尾
|
|
32
|
+
*/
|
|
33
|
+
export function mergeBasicSearchOrder(defaultKeys: string[], sortconfig?: string | string[] | null): string[] {
|
|
34
|
+
const defaults = Array.isArray(defaultKeys) ? defaultKeys.filter(Boolean) : [];
|
|
35
|
+
const saved = Array.isArray(sortconfig) ? sortconfig.filter(Boolean) : splitCsv(sortconfig);
|
|
36
|
+
if (!saved.length) {
|
|
37
|
+
return [...defaults];
|
|
38
|
+
}
|
|
39
|
+
const defaultSet = new Set(defaults);
|
|
40
|
+
const ordered: string[] = [];
|
|
41
|
+
const seen = new Set<string>();
|
|
42
|
+
saved.forEach((key) => {
|
|
43
|
+
if (defaultSet.has(key) && !seen.has(key)) {
|
|
44
|
+
ordered.push(key);
|
|
45
|
+
seen.add(key);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
defaults.forEach((key) => {
|
|
49
|
+
if (!seen.has(key)) {
|
|
50
|
+
ordered.push(key);
|
|
51
|
+
seen.add(key);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return ordered;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 读取当前用户的检索字段顺序(只读 sortconfig)
|
|
59
|
+
* @returns 有序 key 列表;无配置时返回 null
|
|
60
|
+
*/
|
|
61
|
+
export async function loadBasicSearchOrder(
|
|
62
|
+
pageUrl: string,
|
|
63
|
+
api?: SearchFieldsUserTableConfigApi
|
|
64
|
+
): Promise<string[] | null> {
|
|
65
|
+
const url = toBasicSearchConfigUrl(pageUrl);
|
|
66
|
+
try {
|
|
67
|
+
const { getConfigList } = getSearchFieldsUserTableConfigApi(api);
|
|
68
|
+
const res: any = await getConfigList({ url });
|
|
69
|
+
if (res?.code === 200 && res.data?.sortconfig) {
|
|
70
|
+
const keys = splitCsv(res.data.sortconfig);
|
|
71
|
+
return keys.length ? keys : null;
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('[@56os/search-fields] 加载检索字段排序失败', error);
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 保存检索字段顺序。
|
|
81
|
+
* 注意:业务页拖拽落库责任在宿主 Advancedquery;本方法供工具复用/兜底。
|
|
82
|
+
*/
|
|
83
|
+
export async function saveBasicSearchOrder(
|
|
84
|
+
pageUrl: string,
|
|
85
|
+
orderedKeys: string[],
|
|
86
|
+
api?: SearchFieldsUserTableConfigApi
|
|
87
|
+
) {
|
|
88
|
+
const url = toBasicSearchConfigUrl(pageUrl);
|
|
89
|
+
const csv = (orderedKeys || []).filter(Boolean).join(',');
|
|
90
|
+
const { saveConfig } = getSearchFieldsUserTableConfigApi(api);
|
|
91
|
+
return saveConfig({
|
|
92
|
+
url,
|
|
93
|
+
config: csv,
|
|
94
|
+
sortconfig: csv,
|
|
95
|
+
withconfig: ''
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AdvancedQueryFieldRuntime, SearchFieldViewItem } from './types';
|
|
2
|
+
|
|
3
|
+
export type AdvancedQueryParentData = Record<string, Array<Record<string, unknown>>>;
|
|
4
|
+
|
|
5
|
+
/** 页头组件用的 props,不应透传到高级查询 parentdata */
|
|
6
|
+
const UI_ONLY_PROP_KEYS = new Set(['options', 'labelWidth']);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 由 advancedFields + runtime 生成高级查询 parentdata。
|
|
10
|
+
* name 必须来自 schema label(已含 customLabel),禁止业务页再手写一套文案。
|
|
11
|
+
*/
|
|
12
|
+
export function buildAdvancedQueryParentData(
|
|
13
|
+
advancedFields: SearchFieldViewItem[],
|
|
14
|
+
runtime: Record<string, AdvancedQueryFieldRuntime | undefined> = {}
|
|
15
|
+
): AdvancedQueryParentData {
|
|
16
|
+
const parentdata: AdvancedQueryParentData = {};
|
|
17
|
+
(advancedFields || []).forEach((field) => {
|
|
18
|
+
if (!field?.key) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const runtimeItem = runtime[field.key] || {};
|
|
22
|
+
const fieldProps = field.props || {};
|
|
23
|
+
const aqProps: Record<string, unknown> = {};
|
|
24
|
+
Object.keys(fieldProps).forEach((key) => {
|
|
25
|
+
if (!UI_ONLY_PROP_KEYS.has(key)) {
|
|
26
|
+
aqProps[key] = fieldProps[key];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const options = Array.isArray(fieldProps.options) ? (fieldProps.options as unknown[]) : null;
|
|
30
|
+
const base: Record<string, unknown> = {
|
|
31
|
+
type: field.type,
|
|
32
|
+
name: field.label,
|
|
33
|
+
val: runtimeItem.val,
|
|
34
|
+
data: runtimeItem.data ?? options,
|
|
35
|
+
...aqProps,
|
|
36
|
+
...(runtimeItem.extra || {})
|
|
37
|
+
};
|
|
38
|
+
parentdata[field.key] = [base];
|
|
39
|
+
});
|
|
40
|
+
return parentdata;
|
|
41
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-form ref="formRef" :model="model" :label-width="labelWidth" label-position="left">
|
|
3
|
+
<el-row :gutter="10">
|
|
4
|
+
<el-col v-for="field in fields" :key="field.key" :span="resolvedFieldSpan">
|
|
5
|
+
<slot :name="field.key" :field="field" :model="model">
|
|
6
|
+
<el-form-item :label="field.label" :prop="field.key" :label-width="resolveLabelWidth(field)">
|
|
7
|
+
<el-input
|
|
8
|
+
v-if="field.type === 'input' || field.type === 'inputNumber'"
|
|
9
|
+
v-model="model[field.key]"
|
|
10
|
+
:type="field.type === 'inputNumber' ? 'number' : 'text'"
|
|
11
|
+
clearable
|
|
12
|
+
:placeholder="field.queryPlaceholder"
|
|
13
|
+
@keyup.enter="emit('search')"
|
|
14
|
+
/>
|
|
15
|
+
<el-select
|
|
16
|
+
v-else-if="field.type === 'select'"
|
|
17
|
+
v-model="model[field.key]"
|
|
18
|
+
clearable
|
|
19
|
+
filterable
|
|
20
|
+
:placeholder="field.queryPlaceholder"
|
|
21
|
+
style="width: 100%"
|
|
22
|
+
@change="emit('search')"
|
|
23
|
+
>
|
|
24
|
+
<el-option
|
|
25
|
+
v-for="opt in resolveOptions(field)"
|
|
26
|
+
:key="String(opt[resolveOptionValueKey(field)])"
|
|
27
|
+
:label="String(opt[resolveOptionLabelKey(field)] ?? '')"
|
|
28
|
+
:value="opt[resolveOptionValueKey(field)]"
|
|
29
|
+
/>
|
|
30
|
+
</el-select>
|
|
31
|
+
<el-date-picker
|
|
32
|
+
v-else-if="field.type === 'time'"
|
|
33
|
+
v-model="model[field.key]"
|
|
34
|
+
type="date"
|
|
35
|
+
value-format="YYYY-MM-DD"
|
|
36
|
+
format="YYYY-MM-DD"
|
|
37
|
+
clearable
|
|
38
|
+
:placeholder="field.queryPlaceholder"
|
|
39
|
+
style="width: 100%"
|
|
40
|
+
/>
|
|
41
|
+
<el-date-picker
|
|
42
|
+
v-else-if="field.type === 'daterange'"
|
|
43
|
+
v-model="model[field.key]"
|
|
44
|
+
type="daterange"
|
|
45
|
+
range-separator="至"
|
|
46
|
+
start-placeholder="开始日期"
|
|
47
|
+
end-placeholder="结束日期"
|
|
48
|
+
value-format="YYYY-MM-DD"
|
|
49
|
+
format="YYYY-MM-DD"
|
|
50
|
+
clearable
|
|
51
|
+
style="width: 100%"
|
|
52
|
+
/>
|
|
53
|
+
<el-input
|
|
54
|
+
v-else
|
|
55
|
+
v-model="model[field.key]"
|
|
56
|
+
clearable
|
|
57
|
+
:placeholder="field.queryPlaceholder"
|
|
58
|
+
@keyup.enter="emit('search')"
|
|
59
|
+
/>
|
|
60
|
+
</el-form-item>
|
|
61
|
+
</slot>
|
|
62
|
+
</el-col>
|
|
63
|
+
|
|
64
|
+
<el-col :span="resolvedActionsSpan">
|
|
65
|
+
<el-form-item label-width="0">
|
|
66
|
+
<slot name="actions">
|
|
67
|
+
<el-button type="primary" @click="emit('search')">查 询</el-button>
|
|
68
|
+
<el-button @click="emit('reset')">重 置</el-button>
|
|
69
|
+
<el-button v-if="showAdvanced" @click="emit('advanced')">高级查询</el-button>
|
|
70
|
+
</slot>
|
|
71
|
+
</el-form-item>
|
|
72
|
+
</el-col>
|
|
73
|
+
</el-row>
|
|
74
|
+
</el-form>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<script setup lang="ts">
|
|
78
|
+
import { computed, ref } from 'vue';
|
|
79
|
+
import type { SearchFieldViewItem } from '../types';
|
|
80
|
+
|
|
81
|
+
interface Props {
|
|
82
|
+
/** 已排序并截取前 N 的页头字段 */
|
|
83
|
+
fields: SearchFieldViewItem[];
|
|
84
|
+
/** 查询参数对象(与 queryParams 同源) */
|
|
85
|
+
model: Record<string, any>;
|
|
86
|
+
labelWidth?: string | number;
|
|
87
|
+
/**
|
|
88
|
+
* 每个字段占位;不传则自动计算,保证字段 + 查询/重置/高级查询同一行
|
|
89
|
+
*/
|
|
90
|
+
fieldSpan?: number;
|
|
91
|
+
/** 操作按钮列宽;不传则自动计算 */
|
|
92
|
+
actionsSpan?: number;
|
|
93
|
+
/**
|
|
94
|
+
* 是否展示「高级查询」按钮。
|
|
95
|
+
* 约定:可查字段总数 ≤ basicVisibleCount(页头已全展示)时传 false。
|
|
96
|
+
* 由 useSearchFieldSchema 的 showAdvancedQuery 驱动。
|
|
97
|
+
*/
|
|
98
|
+
showAdvanced?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
102
|
+
fields: () => [],
|
|
103
|
+
labelWidth: '80',
|
|
104
|
+
fieldSpan: undefined,
|
|
105
|
+
actionsSpan: undefined,
|
|
106
|
+
showAdvanced: true
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const emit = defineEmits<{
|
|
110
|
+
search: [];
|
|
111
|
+
reset: [];
|
|
112
|
+
advanced: [];
|
|
113
|
+
}>();
|
|
114
|
+
|
|
115
|
+
const formRef = ref();
|
|
116
|
+
|
|
117
|
+
const resolvedActionsSpan = computed(() => {
|
|
118
|
+
if (props.actionsSpan != null) {
|
|
119
|
+
return props.actionsSpan;
|
|
120
|
+
}
|
|
121
|
+
const count = props.fields?.length || 0;
|
|
122
|
+
if (count <= 0) {
|
|
123
|
+
return 24;
|
|
124
|
+
}
|
|
125
|
+
if (count >= 4) {
|
|
126
|
+
return 4;
|
|
127
|
+
}
|
|
128
|
+
if (count === 3) {
|
|
129
|
+
return 6;
|
|
130
|
+
}
|
|
131
|
+
return 8;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const resolvedFieldSpan = computed(() => {
|
|
135
|
+
if (props.fieldSpan != null) {
|
|
136
|
+
return props.fieldSpan;
|
|
137
|
+
}
|
|
138
|
+
const count = props.fields?.length || 0;
|
|
139
|
+
if (count <= 0) {
|
|
140
|
+
return 6;
|
|
141
|
+
}
|
|
142
|
+
const remain = 24 - resolvedActionsSpan.value;
|
|
143
|
+
return Math.min(6, Math.max(4, Math.floor(remain / count)));
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const resolveOptions = (field: SearchFieldViewItem) => {
|
|
147
|
+
const options = field.props?.options;
|
|
148
|
+
return Array.isArray(options) ? options : [];
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const resolveOptionLabelKey = (field: SearchFieldViewItem) => {
|
|
152
|
+
const fieldProps = (field.props?.props as Record<string, string> | undefined) || {};
|
|
153
|
+
return fieldProps.label || 'label';
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const resolveOptionValueKey = (field: SearchFieldViewItem) => {
|
|
157
|
+
const fieldProps = (field.props?.props as Record<string, string> | undefined) || {};
|
|
158
|
+
return fieldProps.value || 'value';
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const resolveLabelWidth = (field: SearchFieldViewItem) => {
|
|
162
|
+
const custom = field.props?.labelWidth;
|
|
163
|
+
if (custom != null) {
|
|
164
|
+
return custom as string | number;
|
|
165
|
+
}
|
|
166
|
+
if ((field.label || '').length >= 5) {
|
|
167
|
+
return '100px';
|
|
168
|
+
}
|
|
169
|
+
return props.labelWidth;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
defineExpose({
|
|
173
|
+
formRef
|
|
174
|
+
});
|
|
175
|
+
</script>
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
SearchFieldType,
|
|
3
|
+
SearchFieldSchemaItem,
|
|
4
|
+
SearchFieldViewItem,
|
|
5
|
+
BasicFieldTag,
|
|
6
|
+
SearchFieldsUserTableConfigApi,
|
|
7
|
+
UseSearchFieldSchemaOptions,
|
|
8
|
+
AdvancedQueryFieldRuntime
|
|
9
|
+
} from './types';
|
|
10
|
+
|
|
11
|
+
export { setupSearchFields, getSearchFieldsUserTableConfigApi } from './setup';
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
BASIC_SEARCH_CONFIG_SUFFIX,
|
|
15
|
+
toBasicSearchConfigUrl,
|
|
16
|
+
mergeBasicSearchOrder,
|
|
17
|
+
loadBasicSearchOrder,
|
|
18
|
+
saveBasicSearchOrder
|
|
19
|
+
} from './basicSearchConfig';
|
|
20
|
+
|
|
21
|
+
export { useSearchFieldSchema } from './useSearchFieldSchema';
|
|
22
|
+
|
|
23
|
+
export { buildAdvancedQueryParentData } from './buildAdvancedQueryParentData';
|
|
24
|
+
export type { AdvancedQueryParentData } from './buildAdvancedQueryParentData';
|
|
25
|
+
|
|
26
|
+
export { default as BasicSearchFields } from './components/BasicSearchFields.vue';
|
package/src/setup.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SearchFieldsUserTableConfigApi } from './types';
|
|
2
|
+
|
|
3
|
+
let userTableConfigApi: SearchFieldsUserTableConfigApi | null = null;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 子应用启动时注入 user-table-config 读写能力。
|
|
7
|
+
* 包内不依赖任一业务仓的 `@/api`。
|
|
8
|
+
*/
|
|
9
|
+
export function setupSearchFields(options: { userTableConfigApi: SearchFieldsUserTableConfigApi }) {
|
|
10
|
+
if (!options?.userTableConfigApi?.getConfigList || !options?.userTableConfigApi?.saveConfig) {
|
|
11
|
+
throw new Error('[@56os/search-fields] setupSearchFields 需要传入 userTableConfigApi.getConfigList / saveConfig');
|
|
12
|
+
}
|
|
13
|
+
userTableConfigApi = options.userTableConfigApi;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getSearchFieldsUserTableConfigApi(
|
|
17
|
+
override?: SearchFieldsUserTableConfigApi
|
|
18
|
+
): SearchFieldsUserTableConfigApi {
|
|
19
|
+
if (override?.getConfigList && override?.saveConfig) {
|
|
20
|
+
return override;
|
|
21
|
+
}
|
|
22
|
+
if (!userTableConfigApi) {
|
|
23
|
+
throw new Error('[@56os/search-fields] 请先调用 setupSearchFields 注入 user-table-config API');
|
|
24
|
+
}
|
|
25
|
+
return userTableConfigApi;
|
|
26
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** 检索控件类型(与高级查询 parentdata.type 对齐) */
|
|
2
|
+
export type SearchFieldType =
|
|
3
|
+
| 'input'
|
|
4
|
+
| 'select'
|
|
5
|
+
| 'remoteSelect'
|
|
6
|
+
| 'customerSelect'
|
|
7
|
+
| 'time'
|
|
8
|
+
| 'daterange'
|
|
9
|
+
| 'inputNumber'
|
|
10
|
+
| 'hidden';
|
|
11
|
+
|
|
12
|
+
/** 页面统一定义:检索字段 schema */
|
|
13
|
+
export interface SearchFieldSchemaItem {
|
|
14
|
+
/** 与 queryParams / 列表 column.value 对齐 */
|
|
15
|
+
key: string;
|
|
16
|
+
/** 默认文案;可被菜单字段 customLabel 覆盖 */
|
|
17
|
+
label: string;
|
|
18
|
+
/** 控件类型 */
|
|
19
|
+
type: SearchFieldType;
|
|
20
|
+
/**
|
|
21
|
+
* 历史字段,可保留。
|
|
22
|
+
* 当前:全部可查字段都进标签/高级查询;页头由排序位置 + basicVisibleCount 决定。
|
|
23
|
+
*/
|
|
24
|
+
basicEligible?: boolean;
|
|
25
|
+
/** @deprecated 不再把字段排除在标签区外 */
|
|
26
|
+
advancedOnly?: boolean;
|
|
27
|
+
/** 占位前缀,缺省「请输入/请选择」 */
|
|
28
|
+
queryPlaceholder?: string;
|
|
29
|
+
/** 控件扩展:options、partyType、multiple、props 等 */
|
|
30
|
+
props?: Record<string, unknown>;
|
|
31
|
+
/** 平台可见性;不传则全平台 */
|
|
32
|
+
platforms?: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 合并文案与排序后的视图字段 */
|
|
36
|
+
export interface SearchFieldViewItem {
|
|
37
|
+
key: string;
|
|
38
|
+
label: string;
|
|
39
|
+
type: SearchFieldType;
|
|
40
|
+
basicEligible?: boolean;
|
|
41
|
+
advancedOnly?: boolean;
|
|
42
|
+
queryPlaceholder: string;
|
|
43
|
+
props?: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 高级查询配置区拖拽标签 */
|
|
47
|
+
export interface BasicFieldTag {
|
|
48
|
+
key: string;
|
|
49
|
+
name: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 子应用注入的 user-table-config API(避免包内强绑 @/api) */
|
|
53
|
+
export interface SearchFieldsUserTableConfigApi {
|
|
54
|
+
getConfigList: (params: { url: string }) => Promise<any>;
|
|
55
|
+
saveConfig: (data: {
|
|
56
|
+
url: string;
|
|
57
|
+
config: string;
|
|
58
|
+
sortconfig: string;
|
|
59
|
+
withconfig: string;
|
|
60
|
+
}) => Promise<any>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface UseSearchFieldSchemaOptions {
|
|
64
|
+
pageUrl: string;
|
|
65
|
+
/** 页面查询区展示前几个,默认 4 */
|
|
66
|
+
basicVisibleCount?: number;
|
|
67
|
+
platformType?: string;
|
|
68
|
+
/** 文案解析:优先菜单 customLabel */
|
|
69
|
+
resolveLabel?: (key: string, fallback: string) => string;
|
|
70
|
+
/** 可选:覆盖全局 setup 注入的 API */
|
|
71
|
+
userTableConfigApi?: SearchFieldsUserTableConfigApi;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 生成高级查询 parentdata 时的运行时值 */
|
|
75
|
+
export interface AdvancedQueryFieldRuntime {
|
|
76
|
+
val?: unknown;
|
|
77
|
+
data?: unknown[];
|
|
78
|
+
extra?: Record<string, unknown>;
|
|
79
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { computed, onMounted, ref, toValue, type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import { loadBasicSearchOrder, mergeBasicSearchOrder } from './basicSearchConfig';
|
|
3
|
+
import type {
|
|
4
|
+
BasicFieldTag,
|
|
5
|
+
SearchFieldSchemaItem,
|
|
6
|
+
SearchFieldViewItem,
|
|
7
|
+
UseSearchFieldSchemaOptions
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
function isPlatformVisible(item: SearchFieldSchemaItem, platformType?: string): boolean {
|
|
11
|
+
if (!item.platforms || item.platforms.length === 0) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (!platformType) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return item.platforms.includes(platformType);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildPlaceholder(item: SearchFieldSchemaItem, label: string): string {
|
|
21
|
+
if (item.queryPlaceholder) {
|
|
22
|
+
return `${item.queryPlaceholder}${label}`;
|
|
23
|
+
}
|
|
24
|
+
const selectLike = item.type === 'select' || item.type === 'remoteSelect' || item.type === 'customerSelect';
|
|
25
|
+
return `${selectLike ? '请选择' : '请输入'}${label}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toViewItem(item: SearchFieldSchemaItem, label: string): SearchFieldViewItem {
|
|
29
|
+
return {
|
|
30
|
+
key: item.key,
|
|
31
|
+
label,
|
|
32
|
+
type: item.type,
|
|
33
|
+
basicEligible: item.basicEligible !== false && !item.advancedOnly,
|
|
34
|
+
advancedOnly: item.advancedOnly === true || item.basicEligible === false,
|
|
35
|
+
queryPlaceholder: buildPlaceholder(item, label),
|
|
36
|
+
props: item.props
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 合并页面 searchFieldSchema + 文案 + 用户排序。
|
|
42
|
+
* - 配置区标签 / 高级查询表单:全部可查字段,顺序一致
|
|
43
|
+
* - 页头查询区:取排序后前 N 个(默认 4)
|
|
44
|
+
*/
|
|
45
|
+
export function useSearchFieldSchema(
|
|
46
|
+
schema: MaybeRefOrGetter<SearchFieldSchemaItem[]>,
|
|
47
|
+
options: UseSearchFieldSchemaOptions
|
|
48
|
+
) {
|
|
49
|
+
const userBasicOrder = ref<string[]>([]);
|
|
50
|
+
const orderLoaded = ref(false);
|
|
51
|
+
const basicVisibleCount = options.basicVisibleCount ?? 4;
|
|
52
|
+
|
|
53
|
+
const resolveLabel = (key: string, fallback: string) => {
|
|
54
|
+
if (typeof options.resolveLabel === 'function') {
|
|
55
|
+
return options.resolveLabel(key, fallback) || fallback;
|
|
56
|
+
}
|
|
57
|
+
return fallback;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const platformFilteredSchema = computed(() => {
|
|
61
|
+
const list = toValue(schema) || [];
|
|
62
|
+
return list.filter((item) => isPlatformVisible(item, options.platformType) && item.type !== 'hidden');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const defaultOrderKeys = computed(() => platformFilteredSchema.value.map((item) => item.key));
|
|
66
|
+
|
|
67
|
+
const orderedFields = computed<SearchFieldViewItem[]>(() => {
|
|
68
|
+
const list = platformFilteredSchema.value;
|
|
69
|
+
const map = new Map(list.map((item) => [item.key, item]));
|
|
70
|
+
const orderedKeys = mergeBasicSearchOrder(defaultOrderKeys.value, userBasicOrder.value);
|
|
71
|
+
return orderedKeys
|
|
72
|
+
.map((key) => map.get(key))
|
|
73
|
+
.filter((item): item is SearchFieldSchemaItem => !!item)
|
|
74
|
+
.map((item) => toViewItem(item, resolveLabel(item.key, item.label)));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const orderedBasicFields = orderedFields;
|
|
78
|
+
|
|
79
|
+
const visibleBasicFields = computed(() => {
|
|
80
|
+
const count = Number(basicVisibleCount);
|
|
81
|
+
if (!Number.isFinite(count) || count <= 0) {
|
|
82
|
+
return [] as SearchFieldViewItem[];
|
|
83
|
+
}
|
|
84
|
+
return orderedFields.value.slice(0, count);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const advancedFields = orderedFields;
|
|
88
|
+
|
|
89
|
+
const basicFieldTags = computed<BasicFieldTag[]>(() =>
|
|
90
|
+
orderedFields.value.map((field) => ({
|
|
91
|
+
key: field.key,
|
|
92
|
+
name: field.label
|
|
93
|
+
}))
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 可查字段总数 ≤ 页头 N(默认 4)时,页头已展示全部字段,不提供高级查询入口。
|
|
98
|
+
* 仅当存在第 N+1 及以后的字段时才展示「高级查询」。
|
|
99
|
+
*/
|
|
100
|
+
const showAdvancedQuery = computed(() => orderedFields.value.length > basicVisibleCount);
|
|
101
|
+
|
|
102
|
+
const applyOrder = (orderedKeys: string[]) => {
|
|
103
|
+
userBasicOrder.value = Array.isArray(orderedKeys) ? [...orderedKeys] : [];
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const loadOrder = async () => {
|
|
107
|
+
const saved = await loadBasicSearchOrder(options.pageUrl, options.userTableConfigApi);
|
|
108
|
+
if (saved?.length) {
|
|
109
|
+
userBasicOrder.value = saved;
|
|
110
|
+
}
|
|
111
|
+
orderLoaded.value = true;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
onMounted(() => {
|
|
115
|
+
loadOrder();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
userBasicOrder,
|
|
120
|
+
orderLoaded,
|
|
121
|
+
orderedFields,
|
|
122
|
+
orderedBasicFields,
|
|
123
|
+
visibleBasicFields,
|
|
124
|
+
advancedFields,
|
|
125
|
+
basicFieldTags,
|
|
126
|
+
showAdvancedQuery,
|
|
127
|
+
applyOrder,
|
|
128
|
+
loadOrder,
|
|
129
|
+
basicVisibleCount
|
|
130
|
+
};
|
|
131
|
+
}
|