qidian-vue-ui 2.0.60 → 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-1406b4d5] {
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-1406b4d5] > .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-1406b4d5] {
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-1406b4d5] th,
13
- .qd-service-table--mini[data-v-1406b4d5] 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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .t-affix > .t-table__affixed-header-elm-wrap,
39
- .qd-service-table[data-v-1406b4d5] .t-affix > .t-table__scrollbar--obvious {
40
- transform: var(--ae7bbf9c);
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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .t-table__expanded-row-inner,
68
- .qd-service-table[data-v-1406b4d5] .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(--v1fc0e4a7) !important;
77
+ width: var(--v3a08d15e) !important;
73
78
  }
74
- .qd-service-table[data-v-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] 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-1406b4d5] tbody > tr.t-table__row--active.t-table__row--hover,
87
- .qd-service-table[data-v-1406b4d5] 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-1406b4d5] .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-1406b4d5] 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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .qd-service-table-filter-item__search,
134
- .qd-service-table[data-v-1406b4d5] .qd-service-table-filter-item__all,
135
- .qd-service-table[data-v-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .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-1406b4d5] .qd-service-table-filter-item .t-checkbox,
159
- .qd-service-table[data-v-1406b4d5] .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-1406b4d5] .qd-service-table-filter-item .t-checkbox__label,
164
- .qd-service-table[data-v-1406b4d5] .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-1406b4d5] .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-1406b4d5] .qd-service-table-filter-item__all,
175
- .qd-service-table--mini[data-v-1406b4d5] .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-1406b4d5"]]);
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
- "ae7bbf9c": affixedHeaderTransform.value,
348
- "v1fc0e4a7": 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) {
@@ -1329,7 +1377,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1329
1377
  });
1330
1378
  }
1331
1379
  function applyCellSelection(cell, event) {
1332
- const column = adaptedColumns.value.find((col) => col.colKey === cell.colKey);
1380
+ const column = leafColumns.value.find((col) => col.colKey === cell.colKey);
1333
1381
  if (!column || !isCellSelectableColumn(column)) return;
1334
1382
  const wasSelected = selectedRowKeys.value.includes(getRowValue(cell.row));
1335
1383
  if (event?.metaKey || event?.ctrlKey) {
@@ -1356,6 +1404,294 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1356
1404
  syncSelectedRowKeysFromCells();
1357
1405
  emitRowSelectChange(cell.row, wasSelected ? "uncheck" : "check");
1358
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
+ };
1694
+ }
1359
1695
  function resolveFormItemLazy(column) {
1360
1696
  const formItem = column.formItem;
1361
1697
  if (!formItem) return false;
@@ -1392,6 +1728,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1392
1728
  }
1393
1729
  function removeEditingCell(target) {
1394
1730
  editingCells.value = editingCells.value.filter((item) => !isSameEditingCell(item, target));
1731
+ editingCellUpdateFns.delete(getCellKey(target.row, target.colKey));
1395
1732
  clearAutofocusEditingCell(target);
1396
1733
  if (!activeEditingCell.value || !isSameEditingCell(activeEditingCell.value, target)) return;
1397
1734
  activeEditingCell.value = editingCells.value.at(-1);
@@ -1495,12 +1832,29 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1495
1832
  if (slots.cellEmptyContent) return slots.cellEmptyContent(context);
1496
1833
  return slots["cell-empty-content"]?.(context) ?? value;
1497
1834
  }
1835
+ function isEditableOrPopupTarget(target) {
1836
+ if (!(target instanceof Element)) return false;
1837
+ return !!target.closest(editableElementSelector) || !!target.closest(popupLayerSelector);
1838
+ }
1498
1839
  function handleDocumentKeydown(event) {
1499
1840
  if (!activated.value) return;
1500
- if (event.key !== "Escape" || event.defaultPrevented) return;
1501
- if (!activeEditingCell.value && !selectedCell.value && !selectedCells.value.length) return;
1502
- const target = event.target;
1503
- 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 = [];
1504
1858
  return;
1505
1859
  }
1506
1860
  const hasCellSelection = !!selectedCell.value || selectedCells.value.length > 0;
@@ -1512,6 +1866,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1512
1866
  }
1513
1867
  exitActiveEditingCell();
1514
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
+ }
1515
1881
  function handleDocumentClick(event) {
1516
1882
  if (!activated.value) return;
1517
1883
  if (!editingCells.value.length && !selectedCell.value && !selectedCells.value.length) return;
@@ -2105,7 +2471,29 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2105
2471
  } : {}
2106
2472
  };
2107
2473
  const columnClassName = reColumn.className === void 0 ? [] : Array.isArray(reColumn.className) ? reColumn.className : [reColumn.className];
2108
- 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
+ };
2109
2497
  const finalizeColumn = (col) => {
2110
2498
  const previousAttrs = col.attrs;
2111
2499
  col.attrs = (context) => ({
@@ -2123,9 +2511,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2123
2511
  };
2124
2512
  if (tableReadonly.value || !formItem) {
2125
2513
  if (!children && formItemColumns.value.length) {
2126
- 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" : ""];
2127
2515
  } else if (!children) {
2128
- reColumn.className = [...columnClassName, selectedCellClass];
2516
+ reColumn.className = [...columnClassName, selectedCellClass, copiedCellClass];
2129
2517
  }
2130
2518
  return finalizeColumn(reColumn);
2131
2519
  }
@@ -2147,7 +2535,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2147
2535
  }
2148
2536
  };
2149
2537
  };
2150
- 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" : ""];
2151
2539
  const generatedEdit = {
2152
2540
  component: QdServiceTableEditCell,
2153
2541
  showEditIcon: false,
@@ -2166,6 +2554,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2166
2554
  row
2167
2555
  };
2168
2556
  const colKey = column.colKey;
2557
+ editingCellUpdateFns.set(getCellKey(row, colKey), context.updateEditedCellValue);
2169
2558
  return {
2170
2559
  row,
2171
2560
  rowValue,
@@ -2229,6 +2618,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2229
2618
  editingCells.value = [];
2230
2619
  activeEditingCell.value = void 0;
2231
2620
  autofocusEditingCell.value = void 0;
2621
+ editingCellUpdateFns.clear();
2232
2622
  }
2233
2623
  function setScrollElTo(position = scrollRecord) {
2234
2624
  const scrollEl = getCurrentTableContentEl();
@@ -2513,6 +2903,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2513
2903
  onMounted(() => {
2514
2904
  document.addEventListener("click", handleDocumentClick);
2515
2905
  document.addEventListener("keydown", handleDocumentKeydown);
2906
+ document.addEventListener("paste", handleDocumentPaste);
2516
2907
  });
2517
2908
  onActivated(async () => {
2518
2909
  const position = {
@@ -2536,6 +2927,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
2536
2927
  animatingAncestorCleanup?.();
2537
2928
  document.removeEventListener("click", handleDocumentClick);
2538
2929
  document.removeEventListener("keydown", handleDocumentKeydown);
2930
+ document.removeEventListener("paste", handleDocumentPaste);
2539
2931
  unmountOffscreenForm();
2540
2932
  });
2541
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.60",
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",
@@ -143,7 +145,10 @@
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"