@ethan1979/jf-lib 0.0.32

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.
Files changed (47) hide show
  1. package/.vscode/extensions.json +3 -0
  2. package/README.md +3 -0
  3. package/babel.config.js +3 -0
  4. package/dist/App.vue.d.ts +2 -0
  5. package/dist/components/index.d.ts +1 -0
  6. package/dist/components/v-table/components/content/components/operations.vue.d.ts +47 -0
  7. package/dist/components/v-table/components/content/index.vue.d.ts +13 -0
  8. package/dist/components/v-table/components/modal-form.vue.d.ts +30 -0
  9. package/dist/components/v-table/components/search.vue.d.ts +12 -0
  10. package/dist/components/v-table/components/toolbar.vue.d.ts +11 -0
  11. package/dist/components/v-table/get-render-function.d.ts +6 -0
  12. package/dist/components/v-table/hooks.d.ts +80 -0
  13. package/dist/components/v-table/index.vue.d.ts +17 -0
  14. package/dist/components/v-table/util.d.ts +2 -0
  15. package/dist/favicon.ico +0 -0
  16. package/dist/jf-lib.es.js +6553 -0
  17. package/dist/jf-lib.umd.js +27 -0
  18. package/dist/main.d.ts +1 -0
  19. package/dist/style.css +1 -0
  20. package/dist/types/global.d.ts +43 -0
  21. package/dist/utils/utils.d.ts +11 -0
  22. package/docs/superpowers/plans/2026-09-19-collapsible-table-search.md +194 -0
  23. package/docs/superpowers/specs/2026-09-19-collapsible-table-search-design.md +80 -0
  24. package/index.html +13 -0
  25. package/package.json +31 -0
  26. package/public/favicon.ico +0 -0
  27. package/scripts/check-collapsible-search.mjs +67 -0
  28. package/src/App.vue +98 -0
  29. package/src/assets/logo.png +0 -0
  30. package/src/components/index.ts +18 -0
  31. package/src/components/v-table/components/content/components/operations.vue +119 -0
  32. package/src/components/v-table/components/content/index.vue +295 -0
  33. package/src/components/v-table/components/modal-form.vue +111 -0
  34. package/src/components/v-table/components/search.vue +181 -0
  35. package/src/components/v-table/components/toolbar.vue +121 -0
  36. package/src/components/v-table/get-render-function.ts +15 -0
  37. package/src/components/v-table/hooks.ts +128 -0
  38. package/src/components/v-table/index.vue +338 -0
  39. package/src/components/v-table/typings.d.ts +164 -0
  40. package/src/components/v-table/util.ts +14 -0
  41. package/src/env.d.ts +12 -0
  42. package/src/main.ts +13 -0
  43. package/src/types/global.ts +48 -0
  44. package/src/utils/utils.ts +77 -0
  45. package/tsconfig.json +38 -0
  46. package/tsconfig.node.json +8 -0
  47. package/vite.config.ts +47 -0
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@ethan1979/jf-lib",
3
+ "private": false,
4
+ "version": "0.0.32",
5
+ "main": "./dist/jf-lib.es.js",
6
+ "typings": "./dist/components/index.d.ts",
7
+ "license": "MIT",
8
+ "scripts": {
9
+ "dev": "vite",
10
+ "build": "vite build && vue-tsc --emitDeclarationOnly",
11
+ "test:collapsible-search": "node scripts/check-collapsible-search.mjs",
12
+ "preview": "vite preview"
13
+ },
14
+ "dependencies": {
15
+ },
16
+ "devDependencies": {
17
+ "@arco-design/web-vue": "latest",
18
+ "@types/lodash": "^4.14.182",
19
+ "@types/node": "^17.0.26",
20
+ "@vitejs/plugin-vue": "^2.3.1",
21
+ "less": "^4.1.2",
22
+ "typescript": "^4.5.4",
23
+ "vite": "^2.9.5",
24
+ "vue-tsc": "^0.34.7",
25
+ "vite-plugin-dts": "^1.1.1",
26
+ "lodash": "^4.17.21",
27
+ "vue": "^3.2.25",
28
+ "@vitejs/plugin-vue-jsx": "^1.2.0",
29
+ "@vue/babel-plugin-jsx": "^1.1.1"
30
+ }
31
+ }
Binary file
@@ -0,0 +1,67 @@
1
+ import assert from 'node:assert/strict'
2
+ import { readFile } from 'node:fs/promises'
3
+
4
+ const [packageSource, searchSource, typingsSource] = await Promise.all([
5
+ readFile(new URL('../package.json', import.meta.url), 'utf8'),
6
+ readFile(
7
+ new URL(
8
+ '../src/components/v-table/components/search.vue',
9
+ import.meta.url
10
+ ),
11
+ 'utf8'
12
+ ),
13
+ readFile(
14
+ new URL('../src/components/v-table/typings.d.ts', import.meta.url),
15
+ 'utf8'
16
+ ),
17
+ ])
18
+
19
+ const pkg = JSON.parse(packageSource)
20
+ const checks = [
21
+ [
22
+ 'declares the collapsible option',
23
+ () => assert.match(typingsSource, /collapsible\?: boolean/),
24
+ ],
25
+ [
26
+ 'declares the default visible fields option',
27
+ () =>
28
+ assert.match(typingsSource, /defaultVisibleFields\?: string\[\]/),
29
+ ],
30
+ [
31
+ 'renders the derived visible field list',
32
+ () => assert.match(searchSource, /in visibleForm/),
33
+ ],
34
+ [
35
+ 'requires collapsible to be explicitly enabled',
36
+ () => assert.match(searchSource, /collapsible\s*===\s*true/),
37
+ ],
38
+ [
39
+ 'exposes the expansion state',
40
+ () => assert.match(searchSource, /:aria-expanded="expanded"/),
41
+ ],
42
+ [
43
+ 'renders both toggle labels',
44
+ () => assert.match(searchSource, /expanded \? '收起' : '展开'/),
45
+ ],
46
+ [
47
+ 'uses the release version',
48
+ () => assert.equal(pkg.version, '0.0.32'),
49
+ ],
50
+ ]
51
+
52
+ const failures = []
53
+ for (const [name, check] of checks) {
54
+ try {
55
+ check()
56
+ } catch {
57
+ failures.push(name)
58
+ }
59
+ }
60
+
61
+ if (failures.length > 0) {
62
+ console.error('Collapsible search contract failed:')
63
+ failures.forEach((failure) => console.error('- ' + failure))
64
+ process.exitCode = 1
65
+ } else {
66
+ console.log('Collapsible search contract is valid.')
67
+ }
package/src/App.vue ADDED
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <div style="height:100vh">
3
+ <v-table :config="config" />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { ref } from "vue";
9
+ import VTable from "./components/v-table/index.vue";
10
+ import { TableConfig } from "./components/v-table/typings";
11
+ // console.log('zzzz',VTable);
12
+ const createData = () => {
13
+ const data: TableConfig = {
14
+ title:'test',
15
+ card:false,
16
+ table: {
17
+ data: [],
18
+ summary:true
19
+ },
20
+ columns: [],
21
+ };
22
+ const keys:string[]=[]
23
+ new Array(5).fill(0).map((_, index) => {
24
+ keys.push(`test${index}-1`,`test${index}-2`)
25
+ data.columns.push( {
26
+ title: `test${index}`,
27
+ dataIndex: `test${index}`,
28
+ children: [
29
+ {
30
+ title: `test${index}-1`,
31
+ dataIndex: `test${index}-1`,
32
+ width:120,
33
+ form:true
34
+ },
35
+ {
36
+ title: `test${index}-2`,
37
+ dataIndex: `test${index}-2`,
38
+ width:120,
39
+ form:true
40
+ },
41
+ ],
42
+ },)
43
+ // console.log("index", index);
44
+ // data.table.data
45
+ });
46
+ keys.forEach(()=>{
47
+ const obj:{[key:string]:number}={}
48
+ keys.forEach(key=>{
49
+ obj[key]=Math.floor(Math.random()*100)
50
+ })
51
+ data.table.data.push(obj)
52
+ })
53
+ return data
54
+ };
55
+
56
+ const config = ref<TableConfig>(createData());
57
+ setTimeout(()=>{
58
+ // config.value.table.summary=true
59
+ config.value.columns.push({
60
+ dataIndex:'title',
61
+ title:'zzz',
62
+ form:{
63
+ hiddenSearch:true
64
+ }
65
+ })
66
+ },100)
67
+ // setTimeout(() => {
68
+ // config.value=createData()
69
+ // // config.value = {
70
+ // // table: {
71
+ // // data: [
72
+ // // {
73
+ // // test: 1,
74
+ // // test2: 2,
75
+ // // },
76
+ // // ],
77
+ // // },
78
+ // // columns: [
79
+ // // {
80
+ // // title: "test1",
81
+ // // dataIndex: "test1",
82
+ // // children: [
83
+ // // {
84
+ // // title: "test",
85
+ // // dataIndex: "test",
86
+ // // },
87
+ // // {
88
+ // // title: "test2",
89
+ // // dataIndex: "test2",
90
+ // // },
91
+ // // ],
92
+ // // },
93
+ // // ],
94
+ // // };
95
+ // }, 1000);
96
+ </script>
97
+
98
+ <style lang="scss" scoped></style>
Binary file
@@ -0,0 +1,18 @@
1
+ // import VTable from './v-table/index.vue'
2
+
3
+ const NAV_HEIGHT = 60
4
+
5
+ const getContainerHeight = () => {
6
+ const { innerHeight } = window
7
+ const height = innerHeight - NAV_HEIGHT - 32
8
+ window.containerHeight = height
9
+ document.body.style.setProperty('--container-height', `${height}px`)
10
+ }
11
+ getContainerHeight()
12
+ window.addEventListener('resize', () => getContainerHeight())
13
+
14
+ // export default {
15
+ // VTable
16
+ // }
17
+
18
+ export {default as VTable} from './v-table/index.vue'
@@ -0,0 +1,119 @@
1
+ <template>
2
+ <span class="column-operations">
3
+ <template v-if="extendRender && props.extendPostion === 'left'">
4
+ <component :is="extendRender" />
5
+ </template>
6
+ <a-button
7
+ v-if="!updateButton?.hidden"
8
+ type="text"
9
+ :disabled="updateButton?.disabled"
10
+ @click="updateData"
11
+ >编辑</a-button
12
+ >
13
+ <template v-if="extendRender && props.extendPostion === 'center'">
14
+ <component :is="extendRender" />
15
+ </template>
16
+ <a-popconfirm content="确定要删除此项内容吗" @ok="deleteData">
17
+ <a-button
18
+ v-if="!deleteButton?.hidden"
19
+ type="text"
20
+ status="danger"
21
+ :disabled="deleteButton?.disabled"
22
+ >删除</a-button
23
+ >
24
+ </a-popconfirm>
25
+ <template v-if="extendRender && props.extendPostion === 'right'">
26
+ <component :is="extendRender" />
27
+ </template>
28
+ </span>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ import { computed, h, onMounted, toRefs } from 'vue'
33
+ import {
34
+ CellRenderFN,
35
+ OperationsButtonConfig,
36
+ TableConfig,
37
+ } from '@/components/v-table/typings'
38
+ import { BaseObj } from '../../../../../types/global'
39
+ import { TableColumnData } from '@arco-design/web-vue';
40
+
41
+ export interface ColumnData {
42
+ record: BaseObj
43
+ column: TableColumnData
44
+ rowIndex: number
45
+ }
46
+
47
+ const props = withDefaults(
48
+ defineProps<{
49
+ config: TableConfig
50
+ columnData: ColumnData
51
+ extend?: CellRenderFN
52
+ extendPostion?: 'center' | 'left' | 'right'
53
+ updateButton?: OperationsButtonConfig
54
+ deleteButton?: OperationsButtonConfig
55
+ }>(),
56
+ {
57
+ extendPostion: 'center',
58
+ }
59
+ )
60
+
61
+ const emit = defineEmits(['updateData', 'deleteData'])
62
+
63
+ const { config, columnData, extend } = toRefs(props)
64
+
65
+ const extendRender = computed(() => {
66
+ if (!columnData) return false
67
+ const { record, column, rowIndex } = columnData?.value as ColumnData
68
+ return () =>
69
+ extend?.value ? extend.value(record, column, rowIndex) : h('span')
70
+ // return false
71
+ })
72
+
73
+ const updateData = async () => {
74
+ if (typeof props.updateButton?.onClick === 'function') {
75
+ const result = await props.updateButton?.onClick(columnData?.value?.record)
76
+ if (result === false) return
77
+ }
78
+ emit('updateData', columnData?.value?.record)
79
+ }
80
+
81
+ const deleteData = async () => {
82
+ if (typeof props.deleteButton?.onClick === 'function') {
83
+ const result = await props.deleteButton?.onClick(columnData?.value?.record)
84
+ if (result === false) return
85
+ }
86
+ emit('deleteData', columnData?.value?.record)
87
+ }
88
+
89
+ onMounted(() => {
90
+ const columns = config?.value?.columns
91
+ if (!columns) return
92
+ const dataIndex = columnData?.value?.column.dataIndex
93
+ for (let i = 0; i < columns.length; i += 1) {
94
+ if (columns[i].dataIndex === dataIndex) {
95
+ const column = columns[i]
96
+ if (column.titleRender) return
97
+ const { title } = column
98
+ column.titleRender = () =>
99
+ h(
100
+ 'span',
101
+ {
102
+ style: {
103
+ marginLeft: '6px',
104
+ },
105
+ },
106
+ title
107
+ )
108
+ }
109
+ }
110
+ })
111
+ </script>
112
+
113
+ <style lang="less">
114
+ .column-operations {
115
+ .arco-btn-size-medium {
116
+ padding: 0 6px;
117
+ }
118
+ }
119
+ </style>
@@ -0,0 +1,295 @@
1
+ <script lang="tsx">
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ import {
4
+ computed,
5
+ defineComponent,
6
+ h,
7
+ inject,
8
+ nextTick,
9
+ onMounted,
10
+ onUnmounted,
11
+ PropType,
12
+ Ref,
13
+ ref,
14
+ toRefs,
15
+ VNode,
16
+ // watch,
17
+ } from 'vue'
18
+ import type _Table from '@arco-design/web-vue/es/table'
19
+ // import type _TableColumn from '@arco-design/web-vue/es/table/table-column'
20
+ import {
21
+ CellComponents,
22
+ CellRenderFN,
23
+ Columns,
24
+ OperationsProps,
25
+ TableConfig,
26
+ } from '../../typings'
27
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
+ import operations from './components/operations.vue'
29
+ import { BaseObj } from '../../../../types/global'
30
+ import { setSelectedRowKeys } from '../../util'
31
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
32
+ import ModalForm from '../modal-form.vue'
33
+ import { Table, TableColumn, TableColumnData } from '@arco-design/web-vue'
34
+ import { omit } from 'lodash'
35
+
36
+ export default defineComponent({
37
+ props: {
38
+ config: {
39
+ type: Object as PropType<TableConfig>,
40
+ },
41
+ },
42
+ setup(props, { emit, expose }) {
43
+ const components = { operations }
44
+
45
+ // type Table = InstanceType<typeof _Table>;
46
+
47
+ // const props = defineProps({
48
+ // config: Object as PropType<TableConfig>,
49
+ // });
50
+
51
+ // const emit = defineEmits(["update", "deleteData", "get"]);
52
+
53
+ const tableRef = ref<InstanceType<typeof Table>>()
54
+ expose({tableRef})
55
+ const { config } = toRefs(props)
56
+
57
+ const TABLE_BASE_CONFIG: typeof Table['$props'] = {
58
+ pagination: {
59
+ current: 1,
60
+ showPageSize: true,
61
+ total: config?.value?.table?.data?.length || 0,
62
+ pageSize: 30,
63
+ showTotal: true,
64
+ showJumper: true,
65
+ },
66
+ rowSelection: {
67
+ type: 'checkbox',
68
+ showCheckedAll: true,
69
+ selectedRowKeys: [],
70
+ },
71
+ }
72
+
73
+ const columns = computed(() => {
74
+ return config?.value?.columns
75
+ })
76
+
77
+ const setPagination = (obj: BaseObj) => {
78
+ if (typeof config?.value?.table.pagination === 'object') {
79
+ config.value.table.pagination = {
80
+ ...config.value.table.pagination,
81
+ ...obj,
82
+ }
83
+ }
84
+ }
85
+ const pageChange = (current: number) => {
86
+ setPagination({ current })
87
+ emit('get')
88
+ }
89
+ const pageSizeChange = (pageSize: number) => {
90
+ setPagination({ pageSize })
91
+ setPagination({ current: 1 })
92
+ emit('get')
93
+ }
94
+
95
+ // const selectedRowKeys = inject<Ref<string[] | undefined>>('selectedRowKeys')
96
+ const selectionChange = (ids: string[] | undefined) => {
97
+ setSelectedRowKeys((config as Ref<TableConfig>).value, ids)
98
+ // if (selectedRowKeys) selectedRowKeys.value = ids
99
+ }
100
+
101
+ const operationsRender = computed(() => {
102
+ let render = false
103
+ columns?.value?.forEach((column) => {
104
+ if (
105
+ typeof column.cellRender === 'object' &&
106
+ column.cellRender.type === 'operations'
107
+ ) {
108
+ render = true
109
+ }
110
+ })
111
+ return render
112
+ })
113
+ const getProps = (
114
+ props:
115
+ | OperationsProps
116
+ | ((params: BaseObj) => OperationsProps)
117
+ | undefined,
118
+ params: any
119
+ ) => {
120
+ if (typeof props === 'function') return props(params)
121
+ return {
122
+ ...props,
123
+ }
124
+ }
125
+
126
+ const visible = ref(false)
127
+ const renderParams = ref<BaseObj>({})
128
+ const modalForm = ref<{
129
+ formData: {
130
+ [x: string]: unknown
131
+ }
132
+ }>()
133
+ const updateData = (params: BaseObj) => {
134
+ renderParams.value = params
135
+ const formData = modalForm.value?.formData as {
136
+ [x: string]: unknown
137
+ }
138
+ Object.keys(formData).forEach((key) => {
139
+ // console.log(' params[key]', params[key], key, formData)
140
+ formData[key] = params[key]
141
+ })
142
+ // 主键,一般为id
143
+ const rowKey = config?.value?.table.rowKey
144
+ if (typeof rowKey === 'string') formData[rowKey] = params[rowKey]
145
+ visible.value = true
146
+ }
147
+ const deleteData = (params: BaseObj) => {
148
+ // console.log('????')
149
+ emit('deleteData', { ...params })
150
+ }
151
+
152
+ const tableId = inject<string>('tableId')
153
+
154
+ const RenderColumns = (columns: Columns<BaseObj>[]) => {
155
+ return columns.map((column) => {
156
+ const slot: {
157
+ title?: () => VNode
158
+ cell?: any
159
+ } = {}
160
+ if (column.titleRender) slot.title = column.titleRender
161
+ if (!column.children && column.cellRender) {
162
+ if (typeof column.cellRender === 'function') {
163
+ slot.cell = column.cellRender
164
+ } else {
165
+ const component = column.cellRender as CellComponents<any>
166
+ slot.cell = (record: any) =>
167
+ h(components[component.type] as any, {
168
+ config: config?.value,
169
+ columnData: {
170
+ ...record,
171
+ // column,
172
+ // rowIndex,
173
+ },
174
+ ...getProps(component?.props, record),
175
+ onUpdateData: updateData,
176
+ onDeleteData: deleteData,
177
+ })
178
+ }
179
+ }
180
+ return (
181
+ <TableColumn
182
+ v-slots={slot}
183
+ ellipsis={true}
184
+ {...(omit(column, [
185
+ 'cellRender',
186
+ 'form',
187
+ 'hidden',
188
+ 'children',
189
+ 'titleRender',
190
+ ]) as any)}
191
+ key={column.dataIndex ?? Date.now()}
192
+ >
193
+ {column.children && RenderColumns(column.children)}
194
+ </TableColumn>
195
+ )
196
+ })
197
+ }
198
+
199
+ onMounted(() => {
200
+ if (config?.value) {
201
+ if (!config.value.batchDelete)
202
+ delete (TABLE_BASE_CONFIG as any).rowSelection
203
+ config.value.table = {
204
+ ...TABLE_BASE_CONFIG,
205
+ ...config.value.table,
206
+ }
207
+ }
208
+ // emit("get");
209
+ // setTimeout(() => resize());
210
+ // window.addEventListener("resize", resize);
211
+ })
212
+ // onUnmounted(() => {
213
+ // // window.removeEventListener("resize", resize);
214
+ // });
215
+
216
+ return () => {
217
+ // console.log("render");
218
+ return (
219
+ <div
220
+ style={{
221
+ flex: 1,
222
+ overflow: 'hidden',
223
+ }}
224
+ >
225
+ <Table
226
+ // 无实际意义,仅为激活表格的flex布局,请勿更改!!!
227
+ scroll={{
228
+ y: 100,
229
+ }}
230
+ {...omit(config.value?.table, 'v-slots')}
231
+ class='v-table-content'
232
+ onPageChange={pageChange}
233
+ onPageSizeChange={pageSizeChange}
234
+ //@ts-ignore
235
+ onSelectionChange={selectionChange}
236
+ ref={tableRef}
237
+ v-slots={{
238
+ ...(config.value?.table?.['v-slots'] || {}),
239
+ columns: () =>
240
+ RenderColumns(
241
+ columns.value?.filter((item) => !item.hidden) || []
242
+ ),
243
+ }}
244
+ ></Table>
245
+ {operationsRender.value && (
246
+ <ModalForm
247
+ {...config.value?.table}
248
+ ref={modalForm}
249
+ title='编辑'
250
+ onUpdate:visible={(v:any) => (visible.value = v)}
251
+ visible={visible.value}
252
+ type='Update'
253
+ renderParams={renderParams.value}
254
+ onOkEvent={(data:any) => emit('update', data)}
255
+ />
256
+ )}
257
+ </div>
258
+ )
259
+ }
260
+ },
261
+ })
262
+ </script>
263
+
264
+ <style lang="less">
265
+ .v-table-content {
266
+ height: 100%;
267
+ .arco-spin {
268
+ overflow: hidden;
269
+ .arco-table-container {
270
+ flex: 1;
271
+ min-height: 0;
272
+ .arco-table-content {
273
+ overflow: hidden;
274
+ .arco-table-body {
275
+ flex: 1;
276
+ min-height: 0;
277
+ max-height: 100% !important;
278
+ height: 0;
279
+ }
280
+ }
281
+ }
282
+ }
283
+ // .arco-table-body {
284
+ // // height: var(--v-table-height);
285
+ // }
286
+ }
287
+ </style>
288
+
289
+ <style lang="less" scoped>
290
+ .v-table-content {
291
+ :deep(.arco-table-pagination) {
292
+ margin-top: 16px;
293
+ }
294
+ }
295
+ </style>