Skip to content

Commit 0347e87

Browse files
uyarnRSS1102liweijie0812github-actions[bot]
authored
chore: release 1.13.1-naruto (#3689)
* feat(Project): add automated management of Project CI (#3680) * chore: update package.json (#3683) * fix(QrCode): fix SVG QRCode not reactive to value prop changes (#3681) * fix(QrCode): fix SVG QRCode not reactive to value prop changes * chore: remove useless attr * chore: add canvas devDep * chore: optimize --------- Co-authored-by: Uyarn <[email protected]> * fix(radio): fix radio warning (#3685) * feat(table): initialize the scroll bar when switching pages (#3684) * feat(table): initialize the scroll bar when switching pages * feat(table): initialize the scroll bar when switching pages * fix(pagination): ensure resetScrollbar is always called * refactor(pagination): replace resetScrollbar function with direct scroll handling in usePagination * chore: release 1.13.1 (#3688) * chore: release 1.13.1 * chore: changelog's changes --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * chore: release 1.13.1-naruto --------- Co-authored-by: 阿菜 Cai <[email protected]> Co-authored-by: liweijie0812 <[email protected]> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 4aadc3d commit 0347e87

File tree

11 files changed

+267
-28
lines changed

11 files changed

+267
-28
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# https://github.com/TDesignOteam/tdesign-projects-action
2+
3+
name: project-issue-trigger
4+
on:
5+
issues:
6+
types: [opened, closed]
7+
jobs:
8+
project-issue-trigger:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: issue to project
12+
uses: TDesignOteam/tdesign-projects-action@main
13+
env:
14+
GH_TOKEN: ${{ secrets.TDESIGN_BOT_TOKEN }}
15+
PROJECT_TYPE: ISSUE2TRIGGER
16+
PROJECT_ID: 1
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# https://github.com/TDesignOteam/tdesign-projects-action
2+
3+
name: projects-label-trigger
4+
5+
on:
6+
issues:
7+
types: [labeled, unlabeled]
8+
9+
jobs:
10+
projects-label-trigger:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: label to project
14+
uses: TDesignOteam/tdesign-projects-action@main
15+
env:
16+
GH_TOKEN: ${{ secrets.TDESIGN_BOT_TOKEN }}
17+
PROJECT_TYPE: LABEL2TRIGGER
18+
PROJECT_ID: 1
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# https://github.com/TDesignOteam/tdesign-projects-action
2+
3+
name: project-pr-trigger
4+
5+
on:
6+
pull_request_target:
7+
types: [opened, reopened, closed]
8+
9+
jobs:
10+
project-pr-trigger:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: pr to project
14+
uses: TDesignOteam/tdesign-projects-action@main
15+
env:
16+
GH_TOKEN: ${{ secrets.TDESIGN_BOT_TOKEN }}
17+
PROJECT_TYPE: PR2TRIGGER
18+
PROJECT_ID: 1

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ toc: false
55
docClass: timeline
66
---
77

8+
## 🌈 1.13.1 `2025-08-20`
9+
### 🚀 Features
10+
- `Table`: 新增切换分页后重置滚动条回到顶部的特性 @RSS1102 ([#3684](https://github.com/Tencent/tdesign-vue/pull/3684))
11+
### 🐞 Bug Fixes
12+
- `QRCode`: 修复 `type='svg'``value` 值变化而二维码未刷新的问题 @RSS1102 ([#3681](https://github.com/Tencent/tdesign-vue/pull/3681))
13+
- `Radio`: 修复 `RadioGroup` 组件的告警问题 @uyarn ([#3685](https://github.com/Tencent/tdesign-vue/pull/3685))
14+
15+
816
## 🌈 1.13.0 `2025-08-07`
917
### 🚀 Features
1018
- `QRCode`: 新增 `QRCode` 二维码组件 @Wesley-0808 ([#3652](https://github.com/Tencent/tdesign-vue/pull/3652))

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "tdesign-vue",
33
"purename": "tdesign",
4-
"version": "1.13.0-naruto",
4+
"version": "1.13.1-naruto",
55
"description": "tdesign-vue",
66
"title": "tdesign-vue",
77
"keywords": [
@@ -140,6 +140,7 @@
140140
"babel-polyfill": "^6.26.0",
141141
"c8": "^7.12.0",
142142
"camelcase": "~6.3.0",
143+
"canvas": "~3.1.2",
143144
"cli-color": "^2.0.0",
144145
"commitizen": "^4.0.3",
145146
"cross-env": "^7.0.2",
@@ -158,7 +159,7 @@
158159
"gray-matter": "^4.0.3",
159160
"husky": "^7.0.4",
160161
"hyphenate": "^0.2.4",
161-
"jsdom": "^25.0.0",
162+
"jsdom": "^26.0.0",
162163
"less": "^4.1.2",
163164
"lint-staged": "^12.3.7",
164165
"mockdate": "^3.0.2",

src/qrcode/__tests__/index.test.jsx

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,6 @@ describe('QRCode', () => {
6464

6565
// it(':iconSize[number|object]-svg', async () => {});
6666

67-
// const level = ['L', 'M', 'Q', 'H'];
68-
// level.forEach((item) => {
69-
// it(`:level[string]-[${item}]`, async () => {
70-
// const wrapper = mount({
71-
// render() {
72-
// return <QRCode level={item} value="https://tdesign.tencent.com/"></QRCode>;
73-
// },
74-
// });
75-
// expect(wrapper.find('.t-qrcode').attributes('level')).eq(item);
76-
// });
77-
// });
78-
7967
it(':size[number]', async () => {
8068
const size = 380;
8169
const wrapper = mount({
@@ -111,5 +99,34 @@ describe('QRCode', () => {
11199
await wrapper.setProps({ status: 'expired', statusRender });
112100
expect(statusRender).toBeCalled();
113101
});
102+
103+
it(':value[string] changes - canvas mode', async () => {
104+
const wrapper = mount({
105+
render() {
106+
return <QRCode type="canvas" value="https://tdesign.tencent.com/"></QRCode>;
107+
},
108+
});
109+
110+
expect(wrapper.find('.t-qrcode').find('canvas').exists()).eq(true);
111+
await wrapper.setProps({ value: 'https://github.com/Tencent/tdesign-vue-next' });
112+
expect(wrapper.find('.t-qrcode').find('canvas').exists()).eq(true);
113+
});
114+
115+
it(':value[string] changes - svg mode', async () => {
116+
const wrapper = mount({
117+
render() {
118+
return <QRCode type="svg" value="https://tdesign.tencent.com/"></QRCode>;
119+
},
120+
});
121+
122+
expect(wrapper.find('.t-qrcode').find('svg').exists()).eq(true);
123+
const initialPaths = wrapper.find('.t-qrcode').findAll('path');
124+
expect(initialPaths.length).eq(2); // 背景path + 前景path
125+
126+
await wrapper.setProps({ value: 'https://github.com/Tencent/tdesign-vue-next' });
127+
const updatedPaths = wrapper.find('.t-qrcode').findAll('path');
128+
expect(updatedPaths.length).eq(2);
129+
expect(wrapper.find('.t-qrcode').find('svg').exists()).eq(true);
130+
});
114131
});
115132
});

src/qrcode/components/qrcode-svg.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,27 @@ export default defineComponent({
99
name: 'QRCodeSVG',
1010
props: QRCodeSubComponentProps,
1111
setup(props) {
12-
const {
13-
margin, cells, numCells, calculatedImageSettings,
14-
} = useQRCode({
12+
const qrCodeData = computed(() => useQRCode({
1513
value: props.value,
1614
level: props.level,
1715
minVersion: DEFAULT_MINVERSION,
1816
includeMargin: DEFAULT_NEED_MARGIN,
1917
marginSize: props.marginSize,
2018
imageSettings: props.imageSettings,
2119
size: props.size,
22-
});
20+
}));
2321

2422
const cellsToDraw = computed(() => {
23+
const { cells, calculatedImageSettings } = qrCodeData.value;
2524
if (props.imageSettings && calculatedImageSettings.value?.excavation != null) {
2625
return excavateModules(cells.value, calculatedImageSettings.value.excavation);
2726
}
2827
return cells.value;
2928
});
3029

30+
const calculatedImageSettings = computed(() => qrCodeData.value.calculatedImageSettings.value);
31+
const margin = computed(() => qrCodeData.value.margin.value);
32+
const numCells = computed(() => qrCodeData.value.numCells.value);
3133
return {
3234
cellsToDraw,
3335
calculatedImageSettings,

src/radio/group.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { CreateElement, VNode, ref } from 'vue';
1+
import { CreateElement, VNode } from 'vue';
22
import { isNumber, isString, throttle } from 'lodash-es';
33
import { CHECKED_CODE_REG } from '../_common/js/common';
44
import { TNodeReturnValue } from '../common';
55
import { getClassPrefixMixins } from '../config-provider/config-receiver';
6-
import useResizeObserver from '../hooks/useResizeObserver';
76
import { off, on } from '../utils/dom';
87
import { emitEvent } from '../utils/event';
98
import mixins from '../utils/mixins';
@@ -34,6 +33,7 @@ export default mixins(classPrefixMixins).extend({
3433

3534
data() {
3635
return {
36+
groupResizeObserver: null,
3737
barStyle: { width: '0px', left: '0px' },
3838
};
3939
},
@@ -100,19 +100,17 @@ export default mixins(classPrefixMixins).extend({
100100

101101
mounted() {
102102
this.calcBarStyle();
103-
this.addKeyboardListeners();
104-
105-
const radioGroupRef = ref(this.$refs.radioGroupRef as HTMLElement);
106-
useResizeObserver(
107-
radioGroupRef,
108-
throttle(() => {
103+
this.groupResizeObserver = this.addResizeObserver(
104+
this.$el,
105+
throttle(async () => {
109106
this.$nextTick(() => this.calcBarStyle());
110107
}, 300),
111108
);
112109
},
113110

114111
beforeDestroy() {
115112
this.removeKeyboardListeners();
113+
this.cleanupResizeObserver(this.groupResizeObserver, this.$el);
116114
},
117115

118116
methods: {
@@ -129,6 +127,21 @@ export default mixins(classPrefixMixins).extend({
129127
}
130128
},
131129

130+
addResizeObserver(el: Element, callback: (data: ResizeObserverEntry[]) => void): ResizeObserver {
131+
const isSupport = typeof window !== 'undefined' && window.ResizeObserver;
132+
if (!isSupport) return;
133+
134+
const containerObserver = new ResizeObserver(callback);
135+
containerObserver.observe(el);
136+
137+
return containerObserver;
138+
},
139+
cleanupResizeObserver(observer: ResizeObserver, container: Element) {
140+
if (!observer || !container) return;
141+
observer.unobserve(container);
142+
observer.disconnect();
143+
},
144+
132145
// 注意:此处会还原区分 数字 和 数字字符串
133146
checkRadioInGroup(e: KeyboardEvent) {
134147
const isCheckedCode = CHECKED_CODE_REG.test(e.key) || CHECKED_CODE_REG.test(e.code);
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { afterEach } from 'vitest';
2+
import { mount } from '@vue/test-utils';
3+
import {
4+
Table, BaseTable, PrimaryTable, EnhancedTable,
5+
} from '@/src/table/index.ts';
6+
7+
// 与项目中其它分页测试风格保持一致,针对受控分页(on page-change)和切页时滚动回顶部进行断言
8+
const TABLES = [Table, BaseTable, PrimaryTable, EnhancedTable];
9+
10+
function createData(total) {
11+
return new Array(total).fill(null).map((_, i) => {
12+
let applicant = `Name${i + 1}`;
13+
if (i === 2) applicant = '王芳';
14+
if (i === 3) applicant = '贾明';
15+
return {
16+
id: i + 1,
17+
index: i + 1,
18+
applicant,
19+
};
20+
});
21+
}
22+
23+
TABLES.forEach((TTable) => {
24+
describe(`${TTable.name} pagination onPageChange & scroll reset`, () => {
25+
afterEach(() => {
26+
document.querySelector('.t-popup')?.remove();
27+
document.querySelector('.t-table')?.remove();
28+
});
29+
30+
it('props.pagination: onPageChange should be triggered when switching pages', async () => {
31+
const onPageChange = vi.fn();
32+
const pagination = {
33+
current: 1,
34+
pageSize: 2,
35+
total: 10,
36+
};
37+
38+
const wrapper = mount({
39+
data() {
40+
return { data: createData(10), pagination };
41+
},
42+
render() {
43+
return (
44+
<TTable
45+
rowKey="index"
46+
data={this.data}
47+
columns={[
48+
{ title: 'index', colKey: 'index' },
49+
{ title: 'applicant', colKey: 'applicant' },
50+
]}
51+
pagination={this.pagination}
52+
on={{ 'page-change': onPageChange }}
53+
></TTable>
54+
);
55+
},
56+
});
57+
58+
expect(wrapper.find('.t-table__pagination').exists()).toBeTruthy();
59+
const nextButton = wrapper.find('.t-pagination__btn-next');
60+
expect(nextButton.exists()).toBeTruthy();
61+
62+
await nextButton.trigger('click');
63+
64+
expect(onPageChange).toHaveBeenCalledTimes(1);
65+
expect(onPageChange).toHaveBeenCalledWith(
66+
expect.objectContaining({ current: 2, pageSize: 2, previous: 1 }),
67+
expect.arrayContaining([
68+
expect.objectContaining({ index: 3, applicant: '王芳' }),
69+
expect.objectContaining({ index: 4, applicant: '贾明' }),
70+
]),
71+
);
72+
});
73+
74+
it('props.pagination: scroll position should reset when switching pages', async () => {
75+
const onPageChange = vi.fn();
76+
const pagination = {
77+
current: 1,
78+
pageSize: 2,
79+
total: 50,
80+
};
81+
82+
const wrapper = mount({
83+
data() {
84+
return { data: createData(50), pagination };
85+
},
86+
render() {
87+
return (
88+
<TTable
89+
rowKey="index"
90+
data={this.data}
91+
columns={[
92+
{ title: 'index', colKey: 'index' },
93+
{ title: 'applicant', colKey: 'applicant' },
94+
]}
95+
pagination={this.pagination}
96+
on={{ 'page-change': onPageChange }}
97+
maxHeight={200}
98+
></TTable>
99+
);
100+
},
101+
});
102+
103+
expect(wrapper.find('.t-table__pagination').exists()).toBeTruthy();
104+
const tableContent = wrapper.find('.t-table__content');
105+
expect(tableContent.exists()).toBeTruthy();
106+
107+
const scrollElement = tableContent.element;
108+
109+
// JSDOM 下 scrollHeight/clientHeight 默认为 0,需要 mock
110+
Object.defineProperty(scrollElement, 'scrollHeight', { value: 100, configurable: true });
111+
Object.defineProperty(scrollElement, 'clientHeight', { value: 50, configurable: true });
112+
113+
expect(scrollElement.scrollHeight).toBeGreaterThan(scrollElement.clientHeight);
114+
expect(scrollElement.scrollTop).toBe(0);
115+
116+
// 模拟滚动到底部
117+
scrollElement.scrollTop = 100;
118+
expect(scrollElement.scrollTop).toBe(100);
119+
120+
const nextButton = wrapper.find('.t-pagination__btn-next');
121+
expect(nextButton.exists()).toBeTruthy();
122+
await nextButton.trigger('click');
123+
124+
expect(onPageChange).toHaveBeenCalledTimes(1);
125+
// 切页后滚动回到顶部
126+
expect(scrollElement.scrollTop).toBe(0);
127+
});
128+
});
129+
});

src/table/base-table.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,11 @@ export default defineComponent({
121121

122122
const {
123123
dataSource, innerPagination, isPaginateData, renderPagination,
124-
} = usePagination(props, context);
124+
} = usePagination(
125+
props,
126+
context,
127+
tableContentRef,
128+
);
125129

126130
const onInnerResizeChange: BaseTableProps['onColumnResizeChange'] = (p) => {
127131
props.onColumnResizeChange?.(p);

0 commit comments

Comments
 (0)