qidian-vue-ui 2.0.59 → 2.0.61

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.
@@ -84,6 +84,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
84
84
  cellSelectable: {
85
85
  type: Boolean
86
86
  },
87
+ cellClipboard: {
88
+ type: Boolean
89
+ },
90
+ onCellCopy: {
91
+ type: Function
92
+ },
93
+ onCellPaste: {
94
+ type: Function
95
+ },
87
96
  reserveSelectedRowOnFilter: {
88
97
  type: Boolean
89
98
  },
@@ -26,6 +26,9 @@ export declare const qdCrudTableProps: {
26
26
  readonly lazyForm: true;
27
27
  readonly editCellValidationBlock: "error";
28
28
  readonly cellSelectable: undefined;
29
+ readonly cellClipboard: true;
30
+ readonly onCellCopy: undefined;
31
+ readonly onCellPaste: undefined;
29
32
  readonly rowKey: "id";
30
33
  readonly expandIcon: true;
31
34
  readonly selectOnRowClick: undefined;
@@ -193,6 +193,9 @@ declare const _default: <D extends TableRowData = TableRowData, R = unknown, P =
193
193
  manual?: boolean | undefined;
194
194
  cellSelectable?: boolean | undefined;
195
195
  selectType?: "single" | "multiple" | undefined;
196
+ cellClipboard?: boolean | undefined;
197
+ onCellCopy?: ((context: import("../../service/table/types").QdServiceTableCellCopyContext<D>) => void) | undefined;
198
+ onCellPaste?: ((context: import("../../service/table/types").QdServiceTableCellPasteContext<D>) => void) | undefined;
196
199
  reserveSelectedRowOnFilter?: boolean | undefined;
197
200
  headerBg?: boolean | undefined;
198
201
  showFilterResult?: boolean | undefined;
@@ -46,6 +46,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
46
46
  selectType: {},
47
47
  activeRowType: { type: [String, Boolean] },
48
48
  cellSelectable: { type: Boolean },
49
+ cellClipboard: { type: Boolean },
50
+ onCellCopy: { type: Function },
51
+ onCellPaste: { type: Function },
49
52
  reserveSelectedRowOnFilter: { type: Boolean },
50
53
  headerBg: { type: Boolean },
51
54
  showFilterResult: { type: Boolean },
@@ -20,6 +20,9 @@ export declare const qdCrudTabsTableProps: {
20
20
  readonly lazyForm: true;
21
21
  readonly editCellValidationBlock: "error";
22
22
  readonly cellSelectable: undefined;
23
+ readonly cellClipboard: true;
24
+ readonly onCellCopy: undefined;
25
+ readonly onCellPaste: undefined;
23
26
  readonly rowKey: "id";
24
27
  readonly expandIcon: true;
25
28
  readonly selectOnRowClick: undefined;
@@ -0,0 +1,8 @@
1
+ export declare function serializeTsv(rows: string[][]): string;
2
+ export declare function parseTsv(text: string): string[][];
3
+ export type CellHtmlCell = {
4
+ label: string;
5
+ value: string;
6
+ };
7
+ export declare function serializeCellHtml(rows: CellHtmlCell[][]): string | null;
8
+ export declare function parseCellHtml(html: string): string[][] | null;
@@ -0,0 +1,44 @@
1
+ function serializeTsv(rows) {
2
+ return rows.map((row) => row.join(" ")).join("\n");
3
+ }
4
+ function parseTsv(text) {
5
+ const normalized = text.replace(/\r\n?/g, "\n");
6
+ if (!normalized) return [];
7
+ const lines = normalized.replace(/\n$/, "").split("\n");
8
+ if (lines.at(-1) === "") lines.pop();
9
+ return lines.map((line) => line.split(" "));
10
+ }
11
+ const CELL_HTML_FLAG = "data-qd-service-table-clipboard";
12
+ const CELL_HTML_VALUE = "data-qd-value";
13
+ function serializeCellHtml(rows) {
14
+ if (typeof document === "undefined") return null;
15
+ const table = document.createElement("table");
16
+ table.setAttribute(CELL_HTML_FLAG, "");
17
+ const tbody = document.createElement("tbody");
18
+ for (const row of rows) {
19
+ const tr = document.createElement("tr");
20
+ for (const cell of row) {
21
+ const td = document.createElement("td");
22
+ td.setAttribute(CELL_HTML_VALUE, cell.value);
23
+ td.textContent = cell.label;
24
+ tr.appendChild(td);
25
+ }
26
+ tbody.appendChild(tr);
27
+ }
28
+ table.appendChild(tbody);
29
+ return table.outerHTML;
30
+ }
31
+ function parseCellHtml(html) {
32
+ if (!html || typeof document === "undefined") return null;
33
+ const doc = new DOMParser().parseFromString(html, "text/html");
34
+ if (!doc.querySelector(`table[${CELL_HTML_FLAG}]`)) return null;
35
+ return Array.from(doc.querySelectorAll("tr")).map(
36
+ (tr) => Array.from(tr.querySelectorAll("td")).map((td) => td.getAttribute(CELL_HTML_VALUE) ?? "")
37
+ );
38
+ }
39
+ export {
40
+ parseCellHtml,
41
+ parseTsv,
42
+ serializeCellHtml,
43
+ serializeTsv
44
+ };
@@ -1,177 +1,235 @@
1
- .qd-service-table[data-v-ea8063e2] {
1
+ .qd-service-table[data-v-d3a788db] {
2
2
  height: 100%;
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  }
6
- .qd-service-table[data-v-ea8063e2] > .t-table__content {
6
+ .qd-service-table[data-v-d3a788db] > .t-table__content {
7
7
  flex: 1;
8
8
  }
9
- .qd-service-table--mini[data-v-ea8063e2] {
9
+ .qd-service-table--mini[data-v-d3a788db] {
10
10
  font: var(--td-font-body-small);
11
11
  }
12
- .qd-service-table--mini[data-v-ea8063e2] th,
13
- .qd-service-table--mini[data-v-ea8063e2] tr:not(.t-table__expanded-row) > td {
12
+ .qd-service-table--mini[data-v-d3a788db] th,
13
+ .qd-service-table--mini[data-v-d3a788db] tr:not(.t-table__expanded-row) > td {
14
14
  padding: var(--td-comp-paddingTB-xxs) var(--td-comp-paddingLR-xxs);
15
15
  line-height: var(--td-line-height-body-small);
16
16
  }
17
- .qd-service-table--mini[data-v-ea8063e2] .t-table__row-full-element {
17
+ .qd-service-table--mini[data-v-d3a788db] .t-table__row-full-element {
18
18
  padding: var(--td-comp-paddingTB-xs) var(--td-comp-paddingLR-s);
19
19
  }
20
- .qd-service-table--hide-empty-image[data-v-ea8063e2] .t-table__empty {
20
+ .qd-service-table--hide-empty-image[data-v-d3a788db] .t-table__empty {
21
21
  min-height: auto;
22
22
  }
23
- .qd-service-table--hide-empty-image[data-v-ea8063e2] .t-table__empty .t-empty {
23
+ .qd-service-table--hide-empty-image[data-v-d3a788db] .t-table__empty .t-empty {
24
24
  padding: 0 !important;
25
25
  }
26
- .qd-service-table--hide-empty-image[data-v-ea8063e2] .t-table__empty .t-empty__image {
26
+ .qd-service-table--hide-empty-image[data-v-d3a788db] .t-table__empty .t-empty__image {
27
27
  display: none;
28
28
  }
29
- .qd-service-table--hide-empty-image[data-v-ea8063e2] .t-table__empty .t-empty__title {
29
+ .qd-service-table--hide-empty-image[data-v-d3a788db] .t-table__empty .t-empty__title {
30
30
  margin-top: 0;
31
31
  }
32
- .qd-service-table[data-v-ea8063e2] .t-table__tree-col:not(:has(.t-table__tree-op-icon)) {
32
+ .qd-service-table[data-v-d3a788db] .t-table__tree-col:not(:has(.t-table__tree-op-icon)) {
33
33
  margin-left: calc(var(--td-font-size-body-large) + var(--td-comp-margin-s));
34
34
  }
35
- .qd-service-table[data-v-ea8063e2] .t-table__tree-leaf-node .t-table__tree-op-icon:empty {
35
+ .qd-service-table[data-v-d3a788db] .t-table__tree-leaf-node .t-table__tree-op-icon:empty {
36
36
  width: calc(var(--td-font-size-body-large) + var(--td-comp-margin-s));
37
37
  }
38
- .qd-service-table[data-v-ea8063e2] .t-affix > .t-table__affixed-header-elm-wrap,
39
- .qd-service-table[data-v-ea8063e2] .t-affix > .t-table__scrollbar--obvious {
40
- transform: var(--f537ab38);
38
+ .qd-service-table[data-v-d3a788db] .t-affix > .t-table__affixed-header-elm-wrap,
39
+ .qd-service-table[data-v-d3a788db] .t-affix > .t-table__scrollbar--obvious {
40
+ transform: var(--v7e0530ae);
41
41
  }
42
- .qd-service-table--horizontal-bar-static[data-v-ea8063e2] .t-table__scrollbar--obvious {
42
+ .qd-service-table--horizontal-bar-static[data-v-d3a788db] .t-table__scrollbar--obvious {
43
43
  visibility: hidden;
44
44
  pointer-events: none;
45
45
  }
46
- .qd-service-table[data-v-ea8063e2] .t-table__expanded-row .qd-service-table--horizontal-bar-static > div:has(> div > .t-table__scrollbar--obvious) {
46
+ .qd-service-table--horizontal-bar-static[data-v-d3a788db] > div:has(> div > .t-table__scrollbar--obvious) {
47
+ height: 0;
48
+ margin-top: 0 !important;
49
+ overflow: hidden;
50
+ }
51
+ .qd-service-table[data-v-d3a788db] .t-table__expanded-row .qd-service-table--horizontal-bar-static > div:has(> div > .t-table__scrollbar--obvious) {
47
52
  display: none;
48
53
  }
49
- .qd-service-table[data-v-ea8063e2] .t-table__expanded-row > td {
54
+ .qd-service-table[data-v-d3a788db] .t-table__expanded-row > td {
50
55
  height: 0;
51
56
  }
52
- .qd-service-table[data-v-ea8063e2] .t-table__affixed-header-elm-wrap {
57
+ .qd-service-table[data-v-d3a788db] .t-table__affixed-header-elm-wrap {
53
58
  height: auto !important;
54
59
  }
55
- .qd-service-table[data-v-ea8063e2] .t-table__affixed-header-elm {
60
+ .qd-service-table[data-v-d3a788db] .t-table__affixed-header-elm {
56
61
  position: relative;
57
62
  scrollbar-width: none;
58
63
  }
59
- .qd-service-table[data-v-ea8063e2] .t-table__affixed-header-elm::-webkit-scrollbar {
64
+ .qd-service-table[data-v-d3a788db] .t-table__affixed-header-elm::-webkit-scrollbar {
60
65
  display: none;
61
66
  width: 0;
62
67
  height: 0;
63
68
  }
64
- .qd-service-table[data-v-ea8063e2] .t-table__virtual-scroll-cursor + table {
69
+ .qd-service-table[data-v-d3a788db] .t-table__virtual-scroll-cursor + table {
65
70
  border-collapse: separate;
66
71
  }
67
- .qd-service-table[data-v-ea8063e2] .t-table__expanded-row-inner,
68
- .qd-service-table[data-v-ea8063e2] .t-table__row-filter-inner {
72
+ .qd-service-table[data-v-d3a788db] .t-table__expanded-row-inner,
73
+ .qd-service-table[data-v-d3a788db] .t-table__row-filter-inner {
69
74
  display: block;
70
75
  position: sticky;
71
76
  left: 0;
72
- width: var(--v787afc59) !important;
77
+ width: var(--v3a08d15e) !important;
73
78
  }
74
- .qd-service-table[data-v-ea8063e2] .t-table__cell-wrap > .t-form__controls > .t-form__controls-content > *:not(.t-switch):not(.t-tag):not(.t-icon):not(.qd-file-icon) {
79
+ .qd-service-table[data-v-d3a788db] .t-table__cell-wrap > .t-form__controls > .t-form__controls-content > *:not(.t-switch):not(.t-tag):not(.t-icon):not(.qd-file-icon) {
75
80
  width: 100%;
76
81
  }
77
- .qd-service-table[data-v-ea8063e2] .qd-service-table__non-editable-col {
82
+ .qd-service-table[data-v-d3a788db] .qd-service-table__non-editable-col {
78
83
  background-color: color-mix(in srgb, var(--td-bg-color-component) 50%, transparent);
79
84
  }
80
- .qd-service-table[data-v-ea8063e2] .qd-service-table__selected-cell {
81
- box-shadow: inset 0 0 0 1px var(--td-brand-color);
85
+ .qd-service-table[data-v-d3a788db] td.qd-service-table__selected-cell {
86
+ box-shadow: inset 0 1px 0 0 var(--qst-sel-t, transparent), inset 0 -1px 0 0 var(--qst-sel-b, transparent), inset 1px 0 0 0 var(--qst-sel-l, transparent), inset -1px 0 0 0 var(--qst-sel-r, transparent);
87
+ }
88
+ .qd-service-table[data-v-d3a788db] .qd-service-table__selected-bt {
89
+ --qst-sel-t: var(--td-brand-color);
90
+ }
91
+ .qd-service-table[data-v-d3a788db] .qd-service-table__selected-bb {
92
+ --qst-sel-b: var(--td-brand-color);
93
+ }
94
+ .qd-service-table[data-v-d3a788db] .qd-service-table__selected-bl {
95
+ --qst-sel-l: var(--td-brand-color);
96
+ }
97
+ .qd-service-table[data-v-d3a788db] .qd-service-table__selected-br {
98
+ --qst-sel-r: var(--td-brand-color);
99
+ }
100
+ .qd-service-table[data-v-d3a788db] td.qd-service-table__copied-cell {
101
+ background-image: var(--qst-copy-t, none), var(--qst-copy-b, none), var(--qst-copy-l, none), var(--qst-copy-r, none);
102
+ background-size: 8px 1px,
103
+ 8px 1px,
104
+ 1px 8px,
105
+ 1px 8px;
106
+ background-position: 0 0,
107
+ 0 100%,
108
+ 0 0,
109
+ 100% 0;
110
+ background-repeat: repeat-x, repeat-x, repeat-y, repeat-y;
111
+ animation: qst-cell-ants-d3a788db 0.5s linear infinite;
112
+ }
113
+ .qd-service-table[data-v-d3a788db] td.qd-service-table__copied-cell.qd-service-table__selected-cell {
114
+ background-size: 8px 2px,
115
+ 8px 2px,
116
+ 2px 8px,
117
+ 2px 8px;
82
118
  }
83
- .qd-service-table[data-v-ea8063e2] tbody > tr.t-table__row--active {
119
+ .qd-service-table[data-v-d3a788db] .qd-service-table__copied-bt {
120
+ --qst-copy-t: linear-gradient(90deg, var(--td-brand-color-hover) 50%, transparent 0);
121
+ }
122
+ .qd-service-table[data-v-d3a788db] .qd-service-table__copied-bb {
123
+ --qst-copy-b: linear-gradient(90deg, var(--td-brand-color-hover) 50%, transparent 0);
124
+ }
125
+ .qd-service-table[data-v-d3a788db] .qd-service-table__copied-bl {
126
+ --qst-copy-l: linear-gradient(0deg, var(--td-brand-color-hover) 50%, transparent 0);
127
+ }
128
+ .qd-service-table[data-v-d3a788db] .qd-service-table__copied-br {
129
+ --qst-copy-r: linear-gradient(0deg, var(--td-brand-color-hover) 50%, transparent 0);
130
+ }
131
+ .qd-service-table[data-v-d3a788db] tbody > tr.t-table__row--active {
84
132
  background-color: var(--td-brand-color-light);
85
133
  }
86
- .qd-service-table[data-v-ea8063e2] tbody > tr.t-table__row--active.t-table__row--hover,
87
- .qd-service-table[data-v-ea8063e2] tbody > tr.t-table__row--active:hover {
134
+ .qd-service-table[data-v-d3a788db] tbody > tr.t-table__row--active.t-table__row--hover,
135
+ .qd-service-table[data-v-d3a788db] tbody > tr.t-table__row--active:hover {
88
136
  background-color: var(--td-brand-color-light-hover);
89
137
  }
90
- .qd-service-table[data-v-ea8063e2] .qd-service-table__non-editable-col:is(
138
+ .qd-service-table[data-v-d3a788db] .qd-service-table__non-editable-col:is(
91
139
  .t-table__cell--fixed-left,
92
140
  .t-table__cell--fixed-right
93
141
  ) {
94
142
  background-color: inherit;
95
143
  background-image: linear-gradient(color-mix(in srgb, var(--td-bg-color-secondarycontainer) 50%, transparent), color-mix(in srgb, var(--td-bg-color-secondarycontainer) 50%, transparent));
96
144
  }
97
- .qd-service-table--disable-horizontal-scroll[data-v-ea8063e2] colgroup > col {
145
+ .qd-service-table--disable-horizontal-scroll[data-v-d3a788db] colgroup > col {
98
146
  width: auto !important;
99
147
  }
100
- .qd-service-table[data-v-ea8063e2] .t-table__filter-pop-content-inner {
148
+ .qd-service-table[data-v-d3a788db] .t-table__filter-pop-content-inner {
101
149
  padding: var(--td-comp-paddingTB-s) var(--td-comp-paddingLR-s) 0;
102
150
  }
103
- .qd-service-table[data-v-ea8063e2] .t-table__filter-pop-content-inner:has(.qd-service-table-filter-item) {
151
+ .qd-service-table[data-v-d3a788db] .t-table__filter-pop-content-inner:has(.qd-service-table-filter-item) {
104
152
  padding: 0;
105
153
  }
106
- .qd-service-table[data-v-ea8063e2] .t-table__filter-pop-content-inner:has(
154
+ .qd-service-table[data-v-d3a788db] .t-table__filter-pop-content-inner:has(
107
155
  .qd-service-table-filter-input,
108
156
  .qd-service-table-filter-custom
109
157
  ) {
110
158
  padding-bottom: var(--td-comp-paddingTB-s);
111
159
  }
112
- .qd-service-table[data-v-ea8063e2] .t-table__filter--bottom-buttons {
160
+ .qd-service-table[data-v-d3a788db] .t-table__filter--bottom-buttons {
113
161
  padding: var(--td-comp-paddingTB-s) var(--td-comp-paddingLR-s);
114
162
  }
115
- .qd-service-table--mini[data-v-ea8063e2] .t-table__filter-pop-content-inner {
163
+ .qd-service-table--mini[data-v-d3a788db] .t-table__filter-pop-content-inner {
116
164
  padding: var(--td-comp-paddingTB-xs) var(--td-comp-paddingLR-xs) 0;
117
165
  }
118
- .qd-service-table--mini[data-v-ea8063e2] .t-table__filter--bottom-buttons {
166
+ .qd-service-table--mini[data-v-d3a788db] .t-table__filter--bottom-buttons {
119
167
  padding: var(--td-comp-paddingTB-xs) var(--td-comp-paddingLR-xs);
120
168
  }
121
- .qd-service-table--mini[data-v-ea8063e2] .t-table__filter-pop-content-inner:has(.qd-service-table-filter-item) {
169
+ .qd-service-table--mini[data-v-d3a788db] .t-table__filter-pop-content-inner:has(.qd-service-table-filter-item) {
122
170
  padding: 0;
123
171
  }
124
- .qd-service-table--mini[data-v-ea8063e2] .t-table__filter-pop-content-inner:has(
172
+ .qd-service-table--mini[data-v-d3a788db] .t-table__filter-pop-content-inner:has(
125
173
  .qd-service-table-filter-input,
126
174
  .qd-service-table-filter-custom
127
175
  ) {
128
176
  padding-bottom: var(--td-comp-paddingTB-xs);
129
177
  }
130
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item {
178
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item {
131
179
  width: 266px;
132
180
  }
133
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item__search,
134
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item__all,
135
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item__options {
181
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item__search,
182
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item__all,
183
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item__options {
136
184
  padding: var(--td-comp-paddingTB-s) var(--td-comp-paddingLR-s) 0;
137
185
  }
138
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item__all {
186
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item__all {
139
187
  display: flex;
140
188
  align-items: center;
141
189
  padding: 0 var(--td-comp-paddingLR-s);
142
190
  border-bottom: 1px solid var(--td-component-stroke);
143
191
  }
144
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item__options {
192
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item__options {
145
193
  display: flex;
146
194
  flex-direction: column;
147
195
  height: 206px;
148
196
  padding: 0 var(--td-comp-paddingLR-s);
149
197
  overflow-y: auto;
150
198
  }
151
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item__count {
199
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item__count {
152
200
  margin-left: var(--td-comp-margin-xs);
153
201
  color: var(--td-text-color-placeholder);
154
202
  }
155
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item__cell {
203
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item__cell {
156
204
  pointer-events: none;
157
205
  }
158
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item .t-checkbox,
159
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item .t-radio {
206
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item .t-checkbox,
207
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item .t-radio {
160
208
  display: flex;
161
209
  align-items: center;
162
210
  }
163
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item .t-checkbox__label,
164
- .qd-service-table[data-v-ea8063e2] .qd-service-table-filter-item .t-radio__label {
211
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item .t-checkbox__label,
212
+ .qd-service-table[data-v-d3a788db] .qd-service-table-filter-item .t-radio__label {
165
213
  display: inline-flex;
166
214
  flex: 1 1 0;
167
215
  align-items: center;
168
216
  min-width: 0;
169
217
  overflow: hidden;
170
218
  }
171
- .qd-service-table--mini[data-v-ea8063e2] .qd-service-table-filter-item__search {
219
+ .qd-service-table--mini[data-v-d3a788db] .qd-service-table-filter-item__search {
172
220
  padding: var(--td-comp-paddingTB-xs) var(--td-comp-paddingLR-xs) 0;
173
221
  }
174
- .qd-service-table--mini[data-v-ea8063e2] .qd-service-table-filter-item__all,
175
- .qd-service-table--mini[data-v-ea8063e2] .qd-service-table-filter-item__options {
222
+ .qd-service-table--mini[data-v-d3a788db] .qd-service-table-filter-item__all,
223
+ .qd-service-table--mini[data-v-d3a788db] .qd-service-table-filter-item__options {
176
224
  padding: 0 var(--td-comp-paddingLR-xs);
177
225
  }
226
+ @keyframes qst-cell-ants-d3a788db {
227
+ to {
228
+ background-position: 8px 0, -8px 100%, 0 -8px, 100% 8px;
229
+ }
230
+ }
231
+ @media (prefers-reduced-motion: reduce) {
232
+ [data-v-d3a788db] td.qd-service-table__copied-cell {
233
+ animation: none;
234
+ }
235
+ }
@@ -2,7 +2,7 @@ import './index.css'
2
2
  import _sfc_main from "./index.vue2.mjs";
3
3
  /* empty css */
4
4
  import _export_sfc from "../../../_virtual/_plugin-vue_export-helper.mjs";
5
- const QdServiceTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-ea8063e2"]]);
5
+ const QdServiceTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d3a788db"]]);
6
6
  export {
7
7
  QdServiceTable as default
8
8
  };
@@ -1,14 +1,15 @@
1
1
  import './index.css'
2
2
  import { defineComponent, mergeDefaults, useCssVars, computed, ref, useSlots, useTemplateRef, shallowRef, inject, getCurrentInstance, reactive, createVNode, unref, watch, nextTick, onMounted, onActivated, onDeactivated, onBeforeUnmount, withDirectives, createBlock, openBlock, resolveDynamicComponent, mergeProps, isRef, createSlots, withCtx, renderList, renderSlot, normalizeProps, guardReactiveProps, h, render } from "vue";
3
3
  import { useVModels, useElementSize, useElementBounding, useIntersectionObserver } from "../../../node_modules/.pnpm/@vueuse_core@14.2.0_vue@3.5.26_typescript@5.9.3_/node_modules/@vueuse/core/dist/index.mjs";
4
- import { useActivated, useTimer, useServicePagination, easyCopy, isEmpty, suspectedWrapperPromise, getScrollEl, createExposeProxy, resizeDirective } from "qidian-shared";
4
+ import { useActivated, useTimer, useServicePagination, easyCopy, isEmpty, suspectedWrapperPromise, getScrollEl, createExposeProxy, resizeDirective, to } from "qidian-shared";
5
5
  import { LoadingIcon, AddRectangleIcon, MinusRectangleIcon } from "tdesign-icons-vue-next";
6
6
  import { EnhancedTable, Table, Empty } from "tdesign-vue-next";
7
7
  import { validate } from "tdesign-vue-next/es/form/utils/form-model.mjs";
8
8
  import { useModal } from "../../../hooks/useModal.mjs";
9
- import { getTransformOffset } from "../../../utils/tools.mjs";
9
+ import { useUICsl, getTransformOffset } from "../../../utils/tools.mjs";
10
10
  import { useConfig } from "../../config-provider/useConfig.mjs";
11
11
  import { getQdFormComponentName, resolveQdFormItemOptions } from "../../form/utils.mjs";
12
+ import { serializeTsv, serializeCellHtml, parseTsv, parseCellHtml } from "./clipboard.mjs";
12
13
  import QdServiceTableEditCell from "./edit-cell.mjs";
13
14
  import QdServiceTableFilterItem from "./filter-item.mjs";
14
15
  import { qdServiceTableTreeConfig, qdServiceTableProps, getSortParams } from "./props.mjs";
@@ -17,8 +18,10 @@ import { useDisabled } from "../../../hooks/useDisabled.mjs";
17
18
  import { PROVIDE_FORM_ADD_BEFORE_SUBMIT_QUEUE } from "../../form/config.mjs";
18
19
  import get from "../../../node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/get.mjs";
19
20
  import _sfc_main$1 from "../../config-renderer/index.vue.mjs";
20
- import QdForm from "../../form/index.vue.mjs";
21
21
  import set from "../../../node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/set.mjs";
22
+ import QdForm from "../../form/index.vue.mjs";
23
+ const popupLayerSelector = ".t-select-input--popup-visible, .t-popup__content, .t-dialog, .t-drawer, .t-image-viewer, .qd-multi-file-viewer";
24
+ const editableElementSelector = 'input, textarea, select, [contenteditable="true"], .ql-editor';
22
25
  const defaultMinWidth = 80;
23
26
  const defaultVirtualScrollBufferSize = 2;
24
27
  const defaultXVirtualScrollThreshold = 20;
@@ -41,6 +44,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
41
44
  cellSelectable: {
42
45
  type: Boolean
43
46
  },
47
+ cellClipboard: {
48
+ type: Boolean
49
+ },
50
+ onCellCopy: {
51
+ type: Function
52
+ },
53
+ onCellPaste: {
54
+ type: Function
55
+ },
44
56
  reserveSelectedRowOnFilter: {
45
57
  type: Boolean
46
58
  },
@@ -344,8 +356,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
344
356
  emit: __emit
345
357
  }) {
346
358
  useCssVars((_ctx) => ({
347
- "f537ab38": affixedHeaderTransform.value,
348
- "v787afc59": expandedRowWidth.value
359
+ "v7e0530ae": affixedHeaderTransform.value,
360
+ "v3a08d15e": expandedRowWidth.value
349
361
  }));
350
362
  const props = __props;
351
363
  const emit = __emit;
@@ -393,8 +405,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
393
405
  t: tableT
394
406
  } = useConfig("table");
395
407
  const {
408
+ msg,
396
409
  msgError
397
410
  } = useModal();
411
+ const csl = useUICsl("QdServiceTable");
398
412
  const tdReadonly = useReadonly();
399
413
  const tdDisabled = useDisabled();
400
414
  const tableReadonly = computed(() => tdReadonly.value || tdDisabled.value);
@@ -406,6 +420,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
406
420
  const selectedCell = shallowRef();
407
421
  const selectedCells = shallowRef([]);
408
422
  const selectedCellAnchor = shallowRef();
423
+ const copiedCells = shallowRef([]);
409
424
  const handledSelectionEvents = /* @__PURE__ */ new WeakSet();
410
425
  const selectionExcludedColKeys = /* @__PURE__ */ new Set(["expand", "drag"]);
411
426
  const editingCellValidationErrors = shallowRef(/* @__PURE__ */ new Map());
@@ -416,10 +431,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
416
431
  } = useActivated();
417
432
  const formItemColumns = computed(() => flattenFormItemColumns(props.columns || []));
418
433
  const editableColumns = computed(() => flattenEditableColumns(props.columns || []));
419
- const reCellSelectable = computed(() => props.cellSelectable ?? (formItemColumns.value.length > 0 || editableColumns.value.length > 0));
434
+ const reCellSelectable = computed(() => props.cellSelectable ?? false);
420
435
  const parentAddBeforeSubmitQueue = inject(PROVIDE_FORM_ADD_BEFORE_SUBMIT_QUEUE, void 0);
421
436
  const beforeSubmitQueueMap = /* @__PURE__ */ new Map();
422
437
  const editingCellValidationVersions = /* @__PURE__ */ new Map();
438
+ const editingCellUpdateFns = /* @__PURE__ */ new Map();
423
439
  parentAddBeforeSubmitQueue?.(async () => {
424
440
  const context = await submitTableData({
425
441
  showErrorMessage: false
@@ -562,6 +578,20 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
562
578
  }
563
579
  return columns;
564
580
  });
581
+ const leafColumns = computed(() => {
582
+ const result = [];
583
+ function visit(items) {
584
+ for (const column of items) {
585
+ if (column.children?.length) {
586
+ visit(column.children);
587
+ continue;
588
+ }
589
+ result.push(column);
590
+ }
591
+ }
592
+ visit(adaptedColumns.value);
593
+ return result;
594
+ });
565
595
  const displayData = computed(() => {
566
596
  if (props.onFilterChange) return data.value;
567
597
  if (props.service && props.pagination !== false) return data.value;
@@ -1258,12 +1288,27 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1258
1288
  function isSelectedCell(row, colKey) {
1259
1289
  return selectedCells.value.some((item) => getRowValue(item.row) === getRowValue(row) && item.colKey === colKey);
1260
1290
  }
1291
+ function isSelectedCellAt(rowIndex, colIndex) {
1292
+ const row = data.value[rowIndex];
1293
+ const column = leafColumns.value[colIndex];
1294
+ if (!row || !column) return false;
1295
+ return isSelectedCell(row, column.colKey);
1296
+ }
1297
+ function isCopiedCell(row, colKey) {
1298
+ return copiedCells.value.some((item) => getRowValue(item.row) === getRowValue(row) && item.colKey === colKey);
1299
+ }
1300
+ function isCopiedCellAt(rowIndex, colIndex) {
1301
+ const row = data.value[rowIndex];
1302
+ const column = leafColumns.value[colIndex];
1303
+ if (!row || !column) return false;
1304
+ return isCopiedCell(row, column.colKey);
1305
+ }
1261
1306
  function hasSelectionModifier(event) {
1262
1307
  return !!event && (event.metaKey || event.ctrlKey || event.shiftKey);
1263
1308
  }
1264
1309
  function expandSelectedCellRange(anchor, target) {
1265
1310
  if (!target.colKey || !anchor.colKey) return [target];
1266
- const columns = adaptedColumns.value;
1311
+ const columns = leafColumns.value;
1267
1312
  const fromCol = columns.findIndex((col) => col.colKey === anchor.colKey);
1268
1313
  const toCol = columns.findIndex((col) => col.colKey === target.colKey);
1269
1314
  const rows = data.value;
@@ -1291,16 +1336,19 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1291
1336
  }
1292
1337
  function syncSelectedRowKeysFromCells() {
1293
1338
  const rowKeys = [];
1339
+ const checkableRowKeys = [];
1294
1340
  const seen = /* @__PURE__ */ new Set();
1295
1341
  for (const item of selectedCells.value) {
1296
1342
  const rowValue = getRowValue(item.row);
1297
1343
  if (seen.has(rowValue)) continue;
1298
- const rowIndex = data.value.findIndex((row) => getRowValue(row) === rowValue);
1299
- if (isRowSelectDisabled(rowIndex >= 0 ? data.value[rowIndex] : item.row, rowIndex)) continue;
1300
1344
  seen.add(rowValue);
1301
1345
  rowKeys.push(rowValue);
1346
+ const rowIndex = data.value.findIndex((row) => getRowValue(row) === rowValue);
1347
+ if (!isRowSelectDisabled(rowIndex >= 0 ? data.value[rowIndex] : item.row, rowIndex)) {
1348
+ checkableRowKeys.push(rowValue);
1349
+ }
1302
1350
  }
1303
- selectedRowKeys.value = rowKeys;
1351
+ selectedRowKeys.value = checkableRowKeys;
1304
1352
  if (props.activeRowType) activeRowKeys.value = rowKeys;
1305
1353
  }
1306
1354
  function pruneDisabledSelectedRows(newData) {
@@ -1318,9 +1366,20 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1318
1366
  if (column.type === "single" || column.type === "multiple") return false;
1319
1367
  return column.cellSelectable ?? String(column.colKey) !== "operation";
1320
1368
  }
1369
+ function emitRowSelectChange(clickedRow, type) {
1370
+ const rowKeys = [...selectedRowKeys.value];
1371
+ const selectedRowData = rowKeys.map((key) => data.value.find((row) => getRowValue(row) === key)).filter((row) => !!row);
1372
+ props.onSelectChange?.(rowKeys, {
1373
+ selectedRowData,
1374
+ currentRowKey: String(getRowValue(clickedRow)),
1375
+ currentRowData: clickedRow,
1376
+ type
1377
+ });
1378
+ }
1321
1379
  function applyCellSelection(cell, event) {
1322
- const column = adaptedColumns.value.find((col) => col.colKey === cell.colKey);
1380
+ const column = leafColumns.value.find((col) => col.colKey === cell.colKey);
1323
1381
  if (!column || !isCellSelectableColumn(column)) return;
1382
+ const wasSelected = selectedRowKeys.value.includes(getRowValue(cell.row));
1324
1383
  if (event?.metaKey || event?.ctrlKey) {
1325
1384
  const exists = selectedCells.value.some((item) => isSameEditingCell(item, cell));
1326
1385
  const rest = selectedCells.value.filter((item) => !isSameEditingCell(item, cell));
@@ -1328,6 +1387,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1328
1387
  selectedCell.value = selectedCells.value.at(-1);
1329
1388
  selectedCellAnchor.value = cell;
1330
1389
  syncSelectedRowKeysFromCells();
1390
+ emitRowSelectChange(cell.row, wasSelected ? "uncheck" : "check");
1331
1391
  return;
1332
1392
  }
1333
1393
  if (event?.shiftKey && selectedCellAnchor.value) {
@@ -1335,12 +1395,302 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1335
1395
  selectedCells.value = expandSelectedCellRange(selectedCellAnchor.value, cell);
1336
1396
  selectedCell.value = cell;
1337
1397
  syncSelectedRowKeysFromCells();
1398
+ emitRowSelectChange(cell.row, wasSelected ? "uncheck" : "check");
1338
1399
  return;
1339
1400
  }
1340
1401
  selectedCells.value = [cell];
1341
1402
  selectedCell.value = cell;
1342
1403
  selectedCellAnchor.value = cell;
1343
1404
  syncSelectedRowKeysFromCells();
1405
+ emitRowSelectChange(cell.row, wasSelected ? "uncheck" : "check");
1406
+ }
1407
+ function resolveCellText(row, colKey) {
1408
+ const value = get(row, colKey);
1409
+ if (value === void 0 || value === null) return "";
1410
+ return String(value).replace(/[\t\n\r]/g, " ");
1411
+ }
1412
+ function resolveSelectedCells() {
1413
+ if (selectedCells.value.length) return selectedCells.value;
1414
+ return selectedCell.value ? [selectedCell.value] : [];
1415
+ }
1416
+ function resolveSelectedCellGrid() {
1417
+ const positions = [];
1418
+ const columns = leafColumns.value;
1419
+ for (const cell of resolveSelectedCells()) {
1420
+ if (!cell.colKey) continue;
1421
+ const colIdx = columns.findIndex((col) => col.colKey === cell.colKey);
1422
+ const rowIdx = data.value.findIndex((row) => getRowValue(row) === getRowValue(cell.row));
1423
+ if (colIdx < 0 || rowIdx < 0) continue;
1424
+ positions.push({
1425
+ rowIdx,
1426
+ colIdx
1427
+ });
1428
+ }
1429
+ if (!positions.length) return null;
1430
+ positions.sort((first, second) => first.rowIdx - second.rowIdx || first.colIdx - second.colIdx);
1431
+ const rows = data.value;
1432
+ const rowStart = positions[0].rowIdx;
1433
+ const rowEnd = positions.at(-1).rowIdx;
1434
+ const colStart = Math.min(...positions.map((item) => item.colIdx));
1435
+ const colEnd = Math.max(...positions.map((item) => item.colIdx));
1436
+ const selectedTds = getCurrentTableContentEl()?.querySelectorAll("td.qd-service-table__selected-cell");
1437
+ const domTds = selectedTds && selectedTds.length === positions.length ? selectedTds : null;
1438
+ const valueGrid = [];
1439
+ const labelGrid = [];
1440
+ const cells = [];
1441
+ let tdIndex = 0;
1442
+ for (let rowIdx = rowStart; rowIdx <= rowEnd; rowIdx++) {
1443
+ const row = rows[rowIdx];
1444
+ const rowValues = [];
1445
+ const rowLabels = [];
1446
+ for (let colIdx = colStart; colIdx <= colEnd; colIdx++) {
1447
+ const position = positions.find((item) => item.rowIdx === rowIdx && item.colIdx === colIdx);
1448
+ const column = columns[colIdx];
1449
+ cells.push({
1450
+ row,
1451
+ colKey: String(column.colKey)
1452
+ });
1453
+ if (!position) {
1454
+ rowValues.push("");
1455
+ rowLabels.push("");
1456
+ continue;
1457
+ }
1458
+ const colKey = String(column.colKey);
1459
+ rowValues.push(resolveCellText(row, colKey));
1460
+ if (column.transformCopy) {
1461
+ const custom = column.transformCopy({
1462
+ row,
1463
+ rowIndex: rowIdx,
1464
+ column,
1465
+ value: get(row, colKey)
1466
+ });
1467
+ if (custom !== void 0) {
1468
+ rowValues[rowValues.length - 1] = normalizeCellText(custom);
1469
+ rowLabels.push(normalizeCellText(custom));
1470
+ continue;
1471
+ }
1472
+ }
1473
+ if (domTds) {
1474
+ const td = domTds[tdIndex++];
1475
+ if (!td.querySelector(".qd-service-table__cell-editor, .t-table__cell--wrap")) {
1476
+ rowLabels.push(normalizeCellText(td.textContent));
1477
+ continue;
1478
+ }
1479
+ }
1480
+ rowLabels.push(resolveCellLabelByRender(column, row) ?? rowValues.at(-1));
1481
+ }
1482
+ valueGrid.push(rowValues);
1483
+ labelGrid.push(rowLabels);
1484
+ }
1485
+ return {
1486
+ valueGrid,
1487
+ labelGrid,
1488
+ cells
1489
+ };
1490
+ }
1491
+ function resolveCellLabelByRender(column, row) {
1492
+ const context = {
1493
+ row,
1494
+ col: column,
1495
+ colIndex: -1,
1496
+ rowIndex: -1,
1497
+ type: "cell"
1498
+ };
1499
+ const result = typeof column.cell === "function" ? column.cell(h, context) : typeof column.render === "function" ? column.render(h, context) : void 0;
1500
+ return typeof result === "string" ? normalizeCellText(result) : void 0;
1501
+ }
1502
+ function normalizeCellText(text) {
1503
+ return (text ?? "").replace(/[\t\n\r]/g, " ").trim();
1504
+ }
1505
+ async function copySelectedCells() {
1506
+ const selection = resolveSelectedCellGrid();
1507
+ if (!selection) return false;
1508
+ const plain = serializeTsv(selection.labelGrid);
1509
+ const valueText = serializeTsv(selection.valueGrid);
1510
+ const html = serializeCellHtml(selection.valueGrid.map((rowValues, rowIdx) => rowValues.map((value, colIdx) => ({
1511
+ label: selection.labelGrid[rowIdx][colIdx],
1512
+ value
1513
+ }))));
1514
+ const [error] = await to(writeClipboard(plain, html));
1515
+ if (error) {
1516
+ csl.warn("单元格复制失败", error);
1517
+ return false;
1518
+ }
1519
+ copiedCells.value = selection.cells;
1520
+ props.onCellCopy?.({
1521
+ text: plain,
1522
+ valueText,
1523
+ cells: selection.cells
1524
+ });
1525
+ return true;
1526
+ }
1527
+ async function writeClipboard(plain, html) {
1528
+ const ClipboardItemCtor = globalThis.ClipboardItem;
1529
+ if (navigator.clipboard && ClipboardItemCtor && html) {
1530
+ await navigator.clipboard.write([new ClipboardItemCtor({
1531
+ "text/plain": new Blob([plain], {
1532
+ type: "text/plain"
1533
+ }),
1534
+ "text/html": new Blob([html], {
1535
+ type: "text/html"
1536
+ })
1537
+ })]);
1538
+ return;
1539
+ }
1540
+ if (navigator.clipboard) {
1541
+ await navigator.clipboard.writeText(plain);
1542
+ return;
1543
+ }
1544
+ const textarea = document.createElement("textarea");
1545
+ textarea.value = plain;
1546
+ textarea.style.position = "fixed";
1547
+ textarea.style.opacity = "0";
1548
+ document.body.appendChild(textarea);
1549
+ textarea.select();
1550
+ const copied = document.execCommand("copy");
1551
+ textarea.remove();
1552
+ if (!copied) throw new Error("[QdServiceTable.writeClipboard] execCommand 复制剪贴板失败");
1553
+ }
1554
+ function canCopySelectedCells(target) {
1555
+ if (!props.cellClipboard || !reCellSelectable.value) return false;
1556
+ if (!selectedCell.value && !selectedCells.value.length) return false;
1557
+ if (isEditableOrPopupTarget(target)) return false;
1558
+ return !document.getSelection()?.toString();
1559
+ }
1560
+ function isPasteTargetCell(column, row) {
1561
+ if (!isCellSelectableColumn(column)) return false;
1562
+ if (column.formItem) return resolveFormItemIfRender(column, row);
1563
+ return !!column.edit;
1564
+ }
1565
+ function writeCellPasteValue(row, colKey, value) {
1566
+ const updateEditedCellValue = editingCellUpdateFns.get(getCellKey(row, colKey));
1567
+ if (updateEditedCellValue) {
1568
+ updateEditedCellValue({
1569
+ isUpdateCurrentRow: true,
1570
+ [colKey]: value
1571
+ });
1572
+ updateEditedCellValue(value);
1573
+ }
1574
+ set(row, colKey, value);
1575
+ }
1576
+ function applyCellPaste(text, html) {
1577
+ const plainGrid = parseTsv(text);
1578
+ const htmlGrid = parseCellHtml(html ?? "");
1579
+ const grid = htmlGrid ?? plainGrid;
1580
+ const anchorCells = resolveSelectedCells();
1581
+ if (!grid.length || !anchorCells.length) return {
1582
+ written: 0,
1583
+ skipped: 0
1584
+ };
1585
+ const columns = leafColumns.value;
1586
+ const rows = data.value;
1587
+ const anchors = anchorCells.map((cell) => ({
1588
+ rowIdx: rows.findIndex((row) => getRowValue(row) === getRowValue(cell.row)),
1589
+ colIdx: columns.findIndex((col) => col.colKey === cell.colKey)
1590
+ })).filter((item) => item.rowIdx >= 0 && item.colIdx >= 0);
1591
+ if (!anchors.length) {
1592
+ msg("粘贴位置已失效,请重新选中单元格后粘贴");
1593
+ return {
1594
+ written: 0,
1595
+ skipped: 0
1596
+ };
1597
+ }
1598
+ const anchorRowIdx = Math.min(...anchors.map((item) => item.rowIdx));
1599
+ const anchorColIdx = Math.min(...anchors.map((item) => item.colIdx));
1600
+ const written = [];
1601
+ let skipped = 0;
1602
+ const singleCell = grid.length === 1 && grid[0].length === 1;
1603
+ const plan = singleCell ? anchors.map(({
1604
+ rowIdx,
1605
+ colIdx
1606
+ }) => ({
1607
+ rowIndex: rowIdx,
1608
+ row: rows[rowIdx],
1609
+ column: columns[colIdx],
1610
+ value: grid[0][0],
1611
+ htmlValue: htmlGrid?.[0]?.[0],
1612
+ label: plainGrid[0]?.[0] ?? ""
1613
+ })) : grid.flatMap((gridRow, rowOffset) => gridRow.map((value, colOffset) => ({
1614
+ rowIndex: anchorRowIdx + rowOffset,
1615
+ row: rows[anchorRowIdx + rowOffset],
1616
+ column: columns[anchorColIdx + colOffset],
1617
+ value,
1618
+ htmlValue: htmlGrid?.[rowOffset]?.[colOffset],
1619
+ label: plainGrid[rowOffset]?.[colOffset] ?? ""
1620
+ })));
1621
+ for (const {
1622
+ rowIndex,
1623
+ row,
1624
+ column,
1625
+ value,
1626
+ htmlValue,
1627
+ label
1628
+ } of plan) {
1629
+ if (!row || !column) continue;
1630
+ if (!isPasteTargetCell(column, row)) {
1631
+ skipped++;
1632
+ continue;
1633
+ }
1634
+ const colKey = String(column.colKey);
1635
+ const writtenValue = column.transformPaste ? column.transformPaste({
1636
+ row,
1637
+ rowIndex,
1638
+ column,
1639
+ value: htmlValue,
1640
+ label
1641
+ }) : value;
1642
+ writeCellPasteValue(row, colKey, writtenValue);
1643
+ written.push({
1644
+ row,
1645
+ colKey,
1646
+ value: writtenValue
1647
+ });
1648
+ }
1649
+ if (!written.length) {
1650
+ msg("当前选中的单元格不可编辑,无法粘贴");
1651
+ return {
1652
+ written: 0,
1653
+ skipped
1654
+ };
1655
+ }
1656
+ if (!singleCell) {
1657
+ const maxRowIdx = Math.min(anchorRowIdx + grid.length - 1, rows.length - 1);
1658
+ const maxColIdx = Math.min(anchorColIdx + grid[0].length - 1, columns.length - 1);
1659
+ const cells = [];
1660
+ for (let rowIdx = anchorRowIdx; rowIdx <= maxRowIdx; rowIdx++) {
1661
+ for (let colIdx = anchorColIdx; colIdx <= maxColIdx; colIdx++) {
1662
+ const row = rows[rowIdx];
1663
+ const column = columns[colIdx];
1664
+ if (!row || !column) continue;
1665
+ cells.push({
1666
+ row,
1667
+ colKey: String(column.colKey)
1668
+ });
1669
+ }
1670
+ }
1671
+ if (cells.length) {
1672
+ selectedCells.value = cells;
1673
+ selectedCell.value = cells.at(-1);
1674
+ selectedCellAnchor.value = cells[0];
1675
+ syncSelectedRowKeysFromCells();
1676
+ }
1677
+ }
1678
+ notifyDataChange();
1679
+ setTimeout(() => {
1680
+ void validateTableData({
1681
+ showErrorMessage: false,
1682
+ scrollToFirstError: false
1683
+ });
1684
+ }, 0);
1685
+ props.onCellPaste?.({
1686
+ text: serializeTsv(grid),
1687
+ written,
1688
+ skipped
1689
+ });
1690
+ return {
1691
+ written: written.length,
1692
+ skipped
1693
+ };
1344
1694
  }
1345
1695
  function resolveFormItemLazy(column) {
1346
1696
  const formItem = column.formItem;
@@ -1378,6 +1728,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1378
1728
  }
1379
1729
  function removeEditingCell(target) {
1380
1730
  editingCells.value = editingCells.value.filter((item) => !isSameEditingCell(item, target));
1731
+ editingCellUpdateFns.delete(getCellKey(target.row, target.colKey));
1381
1732
  clearAutofocusEditingCell(target);
1382
1733
  if (!activeEditingCell.value || !isSameEditingCell(activeEditingCell.value, target)) return;
1383
1734
  activeEditingCell.value = editingCells.value.at(-1);
@@ -1481,12 +1832,29 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1481
1832
  if (slots.cellEmptyContent) return slots.cellEmptyContent(context);
1482
1833
  return slots["cell-empty-content"]?.(context) ?? value;
1483
1834
  }
1835
+ function isEditableOrPopupTarget(target) {
1836
+ if (!(target instanceof Element)) return false;
1837
+ return !!target.closest(editableElementSelector) || !!target.closest(popupLayerSelector);
1838
+ }
1484
1839
  function handleDocumentKeydown(event) {
1485
1840
  if (!activated.value) return;
1486
- if (event.key !== "Escape" || event.defaultPrevented) return;
1487
- if (!activeEditingCell.value && !selectedCell.value && !selectedCells.value.length) return;
1488
- const target = event.target;
1489
- if (target?.closest(".t-select-input--popup-visible, .t-popup__content, .t-dialog, .t-drawer, .t-image-viewer, .qd-multi-file-viewer")) {
1841
+ if (event.defaultPrevented) return;
1842
+ if (event.key === "Escape") {
1843
+ handleSelectionClearKeydown(event.target);
1844
+ return;
1845
+ }
1846
+ if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "c" && canCopySelectedCells(event.target)) {
1847
+ event.preventDefault();
1848
+ void copySelectedCells();
1849
+ }
1850
+ }
1851
+ function handleSelectionClearKeydown(target) {
1852
+ if (!activeEditingCell.value && !selectedCell.value && !selectedCells.value.length && !copiedCells.value.length) return;
1853
+ if (target instanceof Element && target.closest(popupLayerSelector)) {
1854
+ return;
1855
+ }
1856
+ if (copiedCells.value.length) {
1857
+ copiedCells.value = [];
1490
1858
  return;
1491
1859
  }
1492
1860
  const hasCellSelection = !!selectedCell.value || selectedCells.value.length > 0;
@@ -1498,6 +1866,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1498
1866
  }
1499
1867
  exitActiveEditingCell();
1500
1868
  }
1869
+ function handleDocumentPaste(event) {
1870
+ if (!activated.value) return;
1871
+ if (!selectedCell.value && !selectedCells.value.length) return;
1872
+ if (!props.cellClipboard || !reCellSelectable.value) return;
1873
+ if (tableReadonly.value) return;
1874
+ if (isEditableOrPopupTarget(event.target)) return;
1875
+ const text = event.clipboardData?.getData("text/plain");
1876
+ if (!text) return;
1877
+ event.preventDefault();
1878
+ const result = applyCellPaste(text, event.clipboardData?.getData("text/html"));
1879
+ if (result.written > 0) copiedCells.value = [];
1880
+ }
1501
1881
  function handleDocumentClick(event) {
1502
1882
  if (!activated.value) return;
1503
1883
  if (!editingCells.value.length && !selectedCell.value && !selectedCells.value.length) return;
@@ -2091,7 +2471,29 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2091
2471
  } : {}
2092
2472
  };
2093
2473
  const columnClassName = reColumn.className === void 0 ? [] : Array.isArray(reColumn.className) ? reColumn.className : [reColumn.className];
2094
- const selectedCellClass = (context) => context.type === "td" && reCellSelectable.value && isSelectedCell(context.row, column.colKey) ? "qd-service-table__selected-cell" : "";
2474
+ const selectedCellClass = (context) => {
2475
+ if (context.type !== "td" || !reCellSelectable.value) return "";
2476
+ if (!isSelectedCell(context.row, column.colKey)) return "";
2477
+ const rowIndex = data.value.findIndex((item) => getRowValue(item) === getRowValue(context.row));
2478
+ const colIndex = leafColumns.value.findIndex((col) => col.colKey === column.colKey);
2479
+ const classes = ["qd-service-table__selected-cell"];
2480
+ if (!isSelectedCellAt(rowIndex - 1, colIndex)) classes.push("qd-service-table__selected-bt");
2481
+ if (!isSelectedCellAt(rowIndex + 1, colIndex)) classes.push("qd-service-table__selected-bb");
2482
+ if (!isSelectedCellAt(rowIndex, colIndex - 1)) classes.push("qd-service-table__selected-bl");
2483
+ if (!isSelectedCellAt(rowIndex, colIndex + 1)) classes.push("qd-service-table__selected-br");
2484
+ return classes.join(" ");
2485
+ };
2486
+ const copiedCellClass = (context) => {
2487
+ if (context.type !== "td" || !isCopiedCell(context.row, column.colKey)) return "";
2488
+ const rowIndex = data.value.findIndex((item) => getRowValue(item) === getRowValue(context.row));
2489
+ const colIndex = leafColumns.value.findIndex((col) => col.colKey === column.colKey);
2490
+ const classes = ["qd-service-table__copied-cell"];
2491
+ if (!isCopiedCellAt(rowIndex - 1, colIndex)) classes.push("qd-service-table__copied-bt");
2492
+ if (!isCopiedCellAt(rowIndex + 1, colIndex)) classes.push("qd-service-table__copied-bb");
2493
+ if (!isCopiedCellAt(rowIndex, colIndex - 1)) classes.push("qd-service-table__copied-bl");
2494
+ if (!isCopiedCellAt(rowIndex, colIndex + 1)) classes.push("qd-service-table__copied-br");
2495
+ return classes.join(" ");
2496
+ };
2095
2497
  const finalizeColumn = (col) => {
2096
2498
  const previousAttrs = col.attrs;
2097
2499
  col.attrs = (context) => ({
@@ -2109,9 +2511,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2109
2511
  };
2110
2512
  if (tableReadonly.value || !formItem) {
2111
2513
  if (!children && formItemColumns.value.length) {
2112
- reColumn.className = [...columnClassName, selectedCellClass, (context) => context.type === "td" ? "qd-service-table__non-editable-col" : ""];
2514
+ reColumn.className = [...columnClassName, selectedCellClass, copiedCellClass, (context) => context.type === "td" ? "qd-service-table__non-editable-col" : ""];
2113
2515
  } else if (!children) {
2114
- reColumn.className = [...columnClassName, selectedCellClass];
2516
+ reColumn.className = [...columnClassName, selectedCellClass, copiedCellClass];
2115
2517
  }
2116
2518
  return finalizeColumn(reColumn);
2117
2519
  }
@@ -2133,7 +2535,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2133
2535
  }
2134
2536
  };
2135
2537
  };
2136
- reColumn.className = [...columnClassName, "qd-service-table__form-item-col", selectedCellClass, (context) => context.type === "td" && !resolveFormItemIfRender(column, context.row) ? "qd-service-table__non-editable-col" : ""];
2538
+ reColumn.className = [...columnClassName, "qd-service-table__form-item-col", selectedCellClass, copiedCellClass, (context) => context.type === "td" && !resolveFormItemIfRender(column, context.row) ? "qd-service-table__non-editable-col" : ""];
2137
2539
  const generatedEdit = {
2138
2540
  component: QdServiceTableEditCell,
2139
2541
  showEditIcon: false,
@@ -2152,6 +2554,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2152
2554
  row
2153
2555
  };
2154
2556
  const colKey = column.colKey;
2557
+ editingCellUpdateFns.set(getCellKey(row, colKey), context.updateEditedCellValue);
2155
2558
  return {
2156
2559
  row,
2157
2560
  rowValue,
@@ -2215,6 +2618,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2215
2618
  editingCells.value = [];
2216
2619
  activeEditingCell.value = void 0;
2217
2620
  autofocusEditingCell.value = void 0;
2621
+ editingCellUpdateFns.clear();
2218
2622
  }
2219
2623
  function setScrollElTo(position = scrollRecord) {
2220
2624
  const scrollEl = getCurrentTableContentEl();
@@ -2499,6 +2903,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2499
2903
  onMounted(() => {
2500
2904
  document.addEventListener("click", handleDocumentClick);
2501
2905
  document.addEventListener("keydown", handleDocumentKeydown);
2906
+ document.addEventListener("paste", handleDocumentPaste);
2502
2907
  });
2503
2908
  onActivated(async () => {
2504
2909
  const position = {
@@ -2522,6 +2927,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2522
2927
  animatingAncestorCleanup?.();
2523
2928
  document.removeEventListener("click", handleDocumentClick);
2524
2929
  document.removeEventListener("keydown", handleDocumentKeydown);
2930
+ document.removeEventListener("paste", handleDocumentPaste);
2525
2931
  unmountOffscreenForm();
2526
2932
  });
2527
2933
  __expose(createExposeProxy({
@@ -15,6 +15,9 @@ export declare const qdServiceTableProps: {
15
15
  readonly lazyForm: true;
16
16
  readonly editCellValidationBlock: "error";
17
17
  readonly cellSelectable: undefined;
18
+ readonly cellClipboard: true;
19
+ readonly onCellCopy: undefined;
20
+ readonly onCellPaste: undefined;
18
21
  readonly rowKey: "id";
19
22
  readonly expandIcon: true;
20
23
  readonly selectOnRowClick: undefined;
@@ -14,6 +14,9 @@ const qdServiceTableProps = {
14
14
  lazyForm: true,
15
15
  editCellValidationBlock: "error",
16
16
  cellSelectable: void 0,
17
+ cellClipboard: true,
18
+ onCellCopy: void 0,
19
+ onCellPaste: void 0,
17
20
  rowKey: "id",
18
21
  expandIcon: true,
19
22
  selectOnRowClick: void 0,
@@ -12,6 +12,19 @@ export interface QdServiceTableCol<D extends TableRowData = TableRowData> extend
12
12
  children?: QdServiceTableCol<D>[];
13
13
  foot?: string | TNode<BaseTableCellParams<D>>;
14
14
  cellSelectable?: boolean;
15
+ transformCopy?: (context: {
16
+ row: D;
17
+ rowIndex: number;
18
+ column: QdServiceTableCol<D>;
19
+ value: unknown;
20
+ }) => string | undefined;
21
+ transformPaste?: (context: {
22
+ row: D;
23
+ rowIndex: number;
24
+ column: QdServiceTableCol<D>;
25
+ value: string | undefined;
26
+ label: string;
27
+ }) => unknown;
15
28
  filterItem?: true | QdServiceTableFilterItem;
16
29
  formItem?: QdServiceTableFormItem;
17
30
  }
@@ -48,6 +61,23 @@ export interface QdServiceTableDeleteCheckContext<D extends TableRowData = Table
48
61
  childrenKey: string;
49
62
  tableData: D[];
50
63
  }
64
+ export interface QdServiceTableCellCopyContext<D extends TableRowData = TableRowData> {
65
+ text: string;
66
+ valueText: string;
67
+ cells: {
68
+ row: D;
69
+ colKey: string;
70
+ }[];
71
+ }
72
+ export interface QdServiceTableCellPasteContext<D extends TableRowData = TableRowData> {
73
+ text: string;
74
+ written: {
75
+ row: D;
76
+ colKey: string;
77
+ value: unknown;
78
+ }[];
79
+ skipped: number;
80
+ }
51
81
  export interface QdServiceTableValidateParams {
52
82
  showErrorMessage?: boolean;
53
83
  scrollToFirstError?: boolean;
@@ -79,6 +109,9 @@ export interface QdServiceTableProps<D extends TableRowData = TableRowData, R =
79
109
  selectType?: 'single' | 'multiple';
80
110
  activeRowType?: 'single' | 'multiple' | false;
81
111
  cellSelectable?: boolean;
112
+ cellClipboard?: boolean;
113
+ onCellCopy?: (context: QdServiceTableCellCopyContext<D>) => void;
114
+ onCellPaste?: (context: QdServiceTableCellPasteContext<D>) => void;
82
115
  reserveSelectedRowOnFilter?: boolean;
83
116
  headerBg?: boolean;
84
117
  showFilterResult?: boolean;
@@ -69,6 +69,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
69
69
  cellSelectable: {
70
70
  type: Boolean
71
71
  },
72
+ cellClipboard: {
73
+ type: Boolean
74
+ },
75
+ onCellCopy: {
76
+ type: Function
77
+ },
78
+ onCellPaste: {
79
+ type: Function
80
+ },
72
81
  reserveSelectedRowOnFilter: {
73
82
  type: Boolean
74
83
  },
@@ -43,6 +43,9 @@ export declare const qdServiceDialogTableSelectProps: {
43
43
  readonly lazyForm: true;
44
44
  readonly editCellValidationBlock: "error";
45
45
  readonly cellSelectable: undefined;
46
+ readonly cellClipboard: true;
47
+ readonly onCellCopy: undefined;
48
+ readonly onCellPaste: undefined;
46
49
  readonly expandIcon: true;
47
50
  readonly selectOnRowClick: undefined;
48
51
  readonly bordered: undefined;
@@ -83,6 +83,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
83
83
  cellSelectable: {
84
84
  type: Boolean
85
85
  },
86
+ cellClipboard: {
87
+ type: Boolean
88
+ },
89
+ onCellCopy: {
90
+ type: Function
91
+ },
92
+ onCellPaste: {
93
+ type: Function
94
+ },
86
95
  reserveSelectedRowOnFilter: {
87
96
  type: Boolean
88
97
  },
@@ -18,6 +18,9 @@ export declare const qdServiceDialogTabsTableSelectProps: {
18
18
  readonly lazyForm: true;
19
19
  readonly editCellValidationBlock: "error";
20
20
  readonly cellSelectable: undefined;
21
+ readonly cellClipboard: true;
22
+ readonly onCellCopy: undefined;
23
+ readonly onCellPaste: undefined;
21
24
  readonly expandIcon: true;
22
25
  readonly selectOnRowClick: undefined;
23
26
  readonly bordered: undefined;
@@ -59,6 +59,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
59
59
  cellSelectable: {
60
60
  type: Boolean
61
61
  },
62
+ cellClipboard: {
63
+ type: Boolean
64
+ },
65
+ onCellCopy: {
66
+ type: Function
67
+ },
68
+ onCellPaste: {
69
+ type: Function
70
+ },
62
71
  reserveSelectedRowOnFilter: {
63
72
  type: Boolean
64
73
  },
@@ -17,6 +17,9 @@ export declare const qdServicePopupTableSelectProps: {
17
17
  readonly lazyForm: true;
18
18
  readonly editCellValidationBlock: "error";
19
19
  readonly cellSelectable: undefined;
20
+ readonly cellClipboard: true;
21
+ readonly onCellCopy: undefined;
22
+ readonly onCellPaste: undefined;
20
23
  readonly expandIcon: true;
21
24
  readonly selectOnRowClick: undefined;
22
25
  readonly bordered: undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qidian-vue-ui",
3
- "version": "2.0.59",
3
+ "version": "2.0.61",
4
4
  "description": "基于 TDesign 的 QiDian Vue UI 组件库",
5
5
  "author": "qidian",
6
6
  "license": "MIT",
@@ -77,9 +77,11 @@
77
77
  "devDependencies": {
78
78
  "@vitejs/plugin-vue": "^6.0.1",
79
79
  "@vitejs/plugin-vue-jsx": "^5.1.1",
80
+ "@vitest/browser-playwright": "^4.1.11",
80
81
  "@vue/test-utils": "^2.5.0",
81
82
  "baseline-browser-mapping": "^2.11.20",
82
83
  "jsdom": "^29.1.1",
84
+ "playwright": "^1.50.0",
83
85
  "less": "^4.4.2",
84
86
  "less-loader": "^12.3.0",
85
87
  "rollup-plugin-copy": "^3.5.0",
@@ -139,11 +141,14 @@
139
141
  "scripts": {
140
142
  "clean": "rm -rf dist",
141
143
  "dev": "vite --config vite.playground.config.ts",
142
- "build": "npm run test && npm run type-check:test && npm run clean && vue-tsc --noEmit && vite build && vue-tsc && vite build --config vite.autoImportResolver.config.ts",
144
+ "build": "npm run type-check:test && npm run clean && vue-tsc --noEmit && vite build && vue-tsc && vite build --config vite.autoImportResolver.config.ts",
143
145
  "build-only": "npm run clean && vite build && vite build --config vite.autoImportResolver.config.ts",
144
146
  "type-check": "vue-tsc --noEmit",
145
147
  "type-check:test": "vue-tsc --noEmit -p tsconfig.test.json",
148
+ "playwright:install": "playwright install chromium",
146
149
  "test": "vitest run",
150
+ "test:unit": "vitest run --project unit",
151
+ "test:browser": "vitest run --project browser",
147
152
  "test:watch": "vitest",
148
153
  "sync": "cnpm sync qidian-vue-ui",
149
154
  "lint": "eslint --max-warnings 0 \"{src,mock,scripts}/**/*.{vue,ts,tsx,js}\" --fix --cache"