From ad06c98315ab70ebfda4c3ab648d147886ddca14 Mon Sep 17 00:00:00 2001 From: 2betop <2betop.cn@gmail.com> Date: Sun, 18 Feb 2024 17:03:13 +0800 Subject: [PATCH 1/9] =?UTF-8?q?style:=20=E4=BF=AE=E5=A4=8D=20table=20?= =?UTF-8?q?=E5=9C=A8=20safari=20=E9=87=8C=E9=9D=A2=E8=AE=BE=E7=BD=AE=20wid?= =?UTF-8?q?th=20=E6=97=A0=E6=95=88=E9=97=AE=E9=A2=98=20Close:=20#9486?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis-core/src/store/table.ts | 2 +- packages/amis-core/src/utils/browser.ts | 7 +++++++ .../Form/__snapshots__/inputTable.test.tsx.snap | 2 +- packages/amis/src/renderers/Table/ColGroup.tsx | 11 +++++++++-- packages/amis/src/renderers/Table/index.tsx | 2 +- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/amis-core/src/store/table.ts b/packages/amis-core/src/store/table.ts index 8b0276ca0..9a2c4d628 100644 --- a/packages/amis-core/src/store/table.ts +++ b/packages/amis-core/src/store/table.ts @@ -1286,7 +1286,7 @@ export const TableStore = iRendererStore typeof column.pristine.width === 'number' ? `width: ${column.pristine.width}px;` : column.pristine.width - ? `width: ${column.pristine.width};` + ? `width: ${column.pristine.width};min-width: ${column.pristine.width};` : '' // todo 可能需要让修改过列宽的保持相应宽度,目前这样相当于重置了 }`; }); diff --git a/packages/amis-core/src/utils/browser.ts b/packages/amis-core/src/utils/browser.ts index 5cf930432..f222230b4 100644 --- a/packages/amis-core/src/utils/browser.ts +++ b/packages/amis-core/src/utils/browser.ts @@ -2,3 +2,10 @@ export const chromeVersion = (function getChromeVersion() { const raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); return raw ? parseInt(raw[2], 10) : false; })(); + +export const isSafari = + navigator.vendor && + navigator.vendor.indexOf('Apple') > -1 && + navigator.userAgent && + navigator.userAgent.indexOf('CriOS') == -1 && + navigator.userAgent.indexOf('FxiOS') == -1; diff --git a/packages/amis/__tests__/renderers/Form/__snapshots__/inputTable.test.tsx.snap b/packages/amis/__tests__/renderers/Form/__snapshots__/inputTable.test.tsx.snap index ff84c0198..ca2d931ea 100644 --- a/packages/amis/__tests__/renderers/Form/__snapshots__/inputTable.test.tsx.snap +++ b/packages/amis/__tests__/renderers/Form/__snapshots__/inputTable.test.tsx.snap @@ -448,7 +448,7 @@ exports[`Renderer:input table add 1`] = `
th // The problem is min-width CSS property. // Before Chrome 91, min-width was ignored on COL elements. 91 no longer ignores it. - if (typeof chromeVersion === 'number' && chromeVersion < 91) { + // + // 同时 safari 也存在类似问题,设置 colgroup>col 的 width 属性无效 + if (isSafari || (typeof chromeVersion === 'number' && chromeVersion < 91)) { React.useEffect(() => { if (domRef.current) { const ths = [].slice.call( diff --git a/packages/amis/src/renderers/Table/index.tsx b/packages/amis/src/renderers/Table/index.tsx index c114fadfd..58de23f3a 100644 --- a/packages/amis/src/renderers/Table/index.tsx +++ b/packages/amis/src/renderers/Table/index.tsx @@ -1301,8 +1301,8 @@ export default class Table extends React.Component { if (this.resizeLine) { return; } - this.props.store.syncTableWidth(); this.props.store.initTableWidth(); + this.props.store.syncTableWidth(); this.handleOutterScroll(); callback && setTimeout(callback, 20); } From 9cf44211892b63c6113da8c7663122007d847cf2 Mon Sep 17 00:00:00 2001 From: 2betop <2betop.cn@gmail.com> Date: Mon, 19 Feb 2024 11:29:19 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20select=20?= =?UTF-8?q?=E7=9A=84=20autoFill=20=E5=9C=A8=20inputTable=20=E4=B8=AD?= =?UTF-8?q?=E8=B5=8B=E5=80=BC=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20Close:=20#9494?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../renderers/Form/inputTable.test.tsx | 138 ++++++++++++++++++ packages/amis/src/renderers/QuickEdit.tsx | 15 ++ 2 files changed, 153 insertions(+) diff --git a/packages/amis/__tests__/renderers/Form/inputTable.test.tsx b/packages/amis/__tests__/renderers/Form/inputTable.test.tsx index 3238df7a5..374df81e1 100644 --- a/packages/amis/__tests__/renderers/Form/inputTable.test.tsx +++ b/packages/amis/__tests__/renderers/Form/inputTable.test.tsx @@ -809,3 +809,141 @@ test('Renderer:input-table formula', async () => { ] }); }); + +// 对应 github issue: https://github.com/baidu/amis/issues/9494 +test('Renderer:input-table autoFill', async () => { + const onSubmit = jest.fn(); + const {container} = render( + amisRender( + { + type: 'page', + title: 'Hello low code', + body: [ + { + type: 'form', + api: '/api/mock2/form/saveForm', + body: [ + { + type: 'input-table', + name: 'table', + label: '表格表单', + columns: [ + { + label: '名称', + name: 'name', + quickEdit: { + type: 'input-text', + name: 'name', + id: 'u:514910e73695' + }, + id: 'u:97d119520d7c' + }, + { + label: '分数', + name: 'score', + quickEdit: { + type: 'input-number', + name: 'score', + id: 'u:644f5984ff07' + }, + id: 'u:60636ff9ed10' + }, + { + label: '等级', + name: 'level', + quickEdit: { + type: 'select', + name: 'level', + autoFill: { + id: '$id' + }, + id: 'u:38014752298b', + options: [ + { + label: 'a', + value: '111', + id: 111 + }, + { + label: 'a1', + value: '1121', + id: 222 + } + ] + }, + id: 'u:bc682229ad4f' + } + ], + addable: true, + footerAddBtn: { + label: '新增', + icon: 'fa fa-plus', + id: 'u:a0d2d9eab4f7' + }, + strictMode: true, + id: 'u:c296ba75753c', + minLength: 0, + editable: true, + removable: true + } + ], + id: 'u:a2f24ee3ab2d', + debug: true + } + ], + id: 'u:09eedced8bb6', + asideResizor: false, + style: { + boxShadow: ' 0px 0px 0px 0px transparent' + }, + pullRefresh: { + disabled: true + } + }, + { + onSubmit: onSubmit + }, + makeEnv({}) + ) + ); + + await wait(200); + + const add = container.querySelector('.cxd-InputTable-toolbar button'); + fireEvent.click(add!); + await wait(200); + + fireEvent.change(container.querySelector('input[name="name"]')!, { + target: {value: 'a1'} + }); + await wait(200); + + fireEvent.change(container.querySelector('input[name="score"]')!, { + target: {value: '123'} + }); + await wait(200); + + fireEvent.click(container.querySelector('.cxd-Select')!); + await wait(200); + fireEvent.click(container.querySelector('.cxd-Select-menu [role="option"]')!); + await wait(200); + + fireEvent.click(container.querySelector('.cxd-OperationField button')!); + await wait(200); + + const submitBtn = container.querySelector('button[type=submit]'); + fireEvent.click(submitBtn!); + await wait(200); + + expect(onSubmit).toBeCalled(); + expect(onSubmit.mock.calls[0][0]).toEqual({ + table: [ + { + id: 111, + name: 'a1', + score: 123, + level: '111' + } + ] + }); +}); diff --git a/packages/amis/src/renderers/QuickEdit.tsx b/packages/amis/src/renderers/QuickEdit.tsx index fc444b8c2..0b2cd5206 100644 --- a/packages/amis/src/renderers/QuickEdit.tsx +++ b/packages/amis/src/renderers/QuickEdit.tsx @@ -140,6 +140,7 @@ export const HocQuickEdit = this.handleInit = this.handleInit.bind(this); this.handleChange = this.handleChange.bind(this); this.handleFormItemChange = this.handleFormItemChange.bind(this); + this.handleBulkChange = this.handleBulkChange.bind(this); this.state = { isOpened: false @@ -379,6 +380,18 @@ export const HocQuickEdit = ); } + // autoFill 是通过 onBulkChange 触发的 + // quickEdit 需要拦截这个,否则修改的数据就是错的 + handleBulkChange(values: any) { + const {onQuickChange, quickEdit} = this.props; + onQuickChange( + values, + (quickEdit as QuickEditConfig).saveImmediately, + false, + quickEdit as QuickEditConfig + ); + } + openQuickEdit() { currentOpened = this; this.setState({ @@ -595,6 +608,7 @@ export const HocQuickEdit = mode: 'normal', value: getPropValue(this.props) ?? '', onChange: this.handleFormItemChange, + onBulkChange: this.handleBulkChange, ref: this.formItemRef, defaultStatic: false }); @@ -608,6 +622,7 @@ export const HocQuickEdit = simpleMode: true, onInit: this.handleInit, onChange: this.handleChange, + onBulkChange: this.handleBulkChange, formLazyChange: false, canAccessSuperData, disabled, From 2f92d68d79e518fd55bff01dc1a3cfca795e8f9e Mon Sep 17 00:00:00 2001 From: 2betop <2betop.cn@gmail.com> Date: Mon, 19 Feb 2024 14:36:31 +0800 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20mapping=20?= =?UTF-8?q?=E7=BB=93=E5=90=88=E5=85=B6=E4=BB=96=E7=BB=84=E4=BB=B6=E5=85=9C?= =?UTF-8?q?=E5=BA=95=E6=98=BE=E7=A4=BA=E6=97=B6=E5=8F=96=E5=80=BC=E9=94=99?= =?UTF-8?q?=E8=AF=AF=20Close:=20#9613?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../amis/__tests__/renderers/Mapping.test.tsx | 413 +++++++++++------- packages/amis/src/renderers/Mapping.tsx | 8 +- 2 files changed, 259 insertions(+), 162 deletions(-) diff --git a/packages/amis/__tests__/renderers/Mapping.test.tsx b/packages/amis/__tests__/renderers/Mapping.test.tsx index 1df1ca15e..fa5e09984 100644 --- a/packages/amis/__tests__/renderers/Mapping.test.tsx +++ b/packages/amis/__tests__/renderers/Mapping.test.tsx @@ -1,137 +1,156 @@ import {render} from '@testing-library/react'; import '../../src'; import {render as amisRender} from '../../src'; -import {makeEnv} from '../helper'; +import {makeEnv, wait} from '../helper'; -const tag = (label: string) => render( - amisRender( - {type: 'tag', label}, - {}, - makeEnv({}) - ) -).container; +const tag = (label: string) => + render(amisRender({type: 'tag', label}, {}, makeEnv({}))).container; test('Renderer:mapping width object map', async () => { - const setup = (value?: any) => render( - amisRender( - { - type: 'mapping', - map: { - 1: "漂亮", - 2: "开心", - 3: "惊吓", - 4: "紧张", - '*': '其他', + const setup = (value?: any) => + render( + amisRender( + { + type: 'mapping', + map: { + 1: '漂亮', + 2: '开心', + 3: '惊吓', + 4: '紧张', + '*': '其他' + }, + ...(value !== undefined ? {value} : {}) }, - ...(value !== undefined ? {value} : {}) - }, - {}, - makeEnv({}) - ) - ).container; + {}, + makeEnv({}) + ) + ).container; - const noValue = setup().querySelector('.cxd-MappingField .text-muted')! as HTMLElement; + const noValue = setup().querySelector( + '.cxd-MappingField .text-muted' + )! as HTMLElement; expect(noValue.innerHTML).toBe('-'); - const value1 = setup(1).querySelector('.cxd-MappingField .cxd-TplField span')! as HTMLElement; + const value1 = setup(1).querySelector( + '.cxd-MappingField .cxd-TplField span' + )! as HTMLElement; expect(value1.innerHTML).toBe('漂亮'); - const value5 = setup(5).querySelector('.cxd-MappingField .cxd-TplField span')! as HTMLElement; + const value5 = setup(5).querySelector( + '.cxd-MappingField .cxd-TplField span' + )! as HTMLElement; expect(value5.innerHTML).toBe('其他'); }); test('Renderer:mapping width array map', async () => { - const setup = (value?: any) => render( - amisRender( - { - type: 'mapping', - map: [ - {1: "漂亮"}, - {2: "开心"}, - {3: "惊吓"}, - {4: "紧张"}, - {'*': '其他'} - ], - ...(value !== undefined ? {value} : {}) - }, - {}, - makeEnv({}) - ) - ).container; + const setup = (value?: any) => + render( + amisRender( + { + type: 'mapping', + map: [ + {1: '漂亮'}, + {2: '开心'}, + {3: '惊吓'}, + {4: '紧张'}, + {'*': '其他'} + ], + ...(value !== undefined ? {value} : {}) + }, + {}, + makeEnv({}) + ) + ).container; - const noValue = setup().querySelector('.cxd-MappingField .text-muted')! as HTMLElement; + const noValue = setup().querySelector( + '.cxd-MappingField .text-muted' + )! as HTMLElement; expect(noValue.innerHTML).toBe('-'); - const value1 = setup(1).querySelector('.cxd-MappingField .cxd-TplField span')! as HTMLElement; + const value1 = setup(1).querySelector( + '.cxd-MappingField .cxd-TplField span' + )! as HTMLElement; expect(value1.innerHTML).toBe('漂亮'); - const value5 = setup(5).querySelector('.cxd-MappingField .cxd-TplField span')! as HTMLElement; + const value5 = setup(5).querySelector( + '.cxd-MappingField .cxd-TplField span' + )! as HTMLElement; expect(value5.innerHTML).toBe('其他'); }); test('Renderer:mapping attr: valueField and labelField', async () => { - const setup = (value?: any) => render( - amisRender( - { - type: 'mapping', - map: [ - { - name: 1, - text: '漂亮' - }, - { - name: 2, - text: '开心' - }, - { - name: '*', - text: '其他' - } - ], - labelField: 'text', - valueField: 'name', - ...(value !== undefined ? {value} : {}) - }, - {}, - makeEnv({}) - ) - ).container; + const setup = (value?: any) => + render( + amisRender( + { + type: 'mapping', + map: [ + { + name: 1, + text: '漂亮' + }, + { + name: 2, + text: '开心' + }, + { + name: '*', + text: '其他' + } + ], + labelField: 'text', + valueField: 'name', + ...(value !== undefined ? {value} : {}) + }, + {}, + makeEnv({}) + ) + ).container; - const noValue = setup().querySelector('.cxd-MappingField .text-muted')! as HTMLElement; + const noValue = setup().querySelector( + '.cxd-MappingField .text-muted' + )! as HTMLElement; expect(noValue.innerHTML).toBe('-'); - const value1 = setup(1).querySelector('.cxd-MappingField .cxd-TplField span')! as HTMLElement; + const value1 = setup(1).querySelector( + '.cxd-MappingField .cxd-TplField span' + )! as HTMLElement; expect(value1.innerHTML).toBe('漂亮'); - const value5 = setup(5).querySelector('.cxd-MappingField .cxd-TplField span')! as HTMLElement; + const value5 = setup(5).querySelector( + '.cxd-MappingField .cxd-TplField span' + )! as HTMLElement; expect(value5.innerHTML).toBe('其他'); }); test('Renderer:mapping attr: itemSchema when simple map', async () => { - const setup = (value?: any) => render( - amisRender( - { - type: 'mapping', - map: [ - {1: "漂亮"}, - {2: "开心"}, - {3: "惊吓"}, - {4: "紧张"}, - {'*': '其他'} - ], - valueField: 'name', - ...(value !== undefined ? {value} : {}), - itemSchema: { - type: 'tag', - label: '${item}' - } - }, - {}, - makeEnv({}) - ) - ).container; + const setup = (value?: any) => + render( + amisRender( + { + type: 'mapping', + map: [ + {1: '漂亮'}, + {2: '开心'}, + {3: '惊吓'}, + {4: '紧张'}, + {'*': '其他'} + ], + valueField: 'name', + ...(value !== undefined ? {value} : {}), + itemSchema: { + type: 'tag', + label: '${item}' + } + }, + {}, + makeEnv({}) + ) + ).container; - const noValue = setup().querySelector('.cxd-MappingField .text-muted')! as HTMLElement; + const noValue = setup().querySelector( + '.cxd-MappingField .text-muted' + )! as HTMLElement; expect(noValue.innerHTML).toBe('-'); const value1 = setup(1).querySelector('.cxd-MappingField')! as HTMLElement; @@ -142,37 +161,40 @@ test('Renderer:mapping attr: itemSchema when simple map', async () => { }); test('Renderer:mapping attr: itemSchema when normal map', async () => { - const setup = (value?: any) => render( - amisRender( - { - type: 'mapping', - map: [ - { - name: 1, - text: '漂亮' - }, - { - name: 2, - text: '开心' - }, - { - name: '*', - text: '其他' + const setup = (value?: any) => + render( + amisRender( + { + type: 'mapping', + map: [ + { + name: 1, + text: '漂亮' + }, + { + name: 2, + text: '开心' + }, + { + name: '*', + text: '其他' + } + ], + valueField: 'name', + ...(value !== undefined ? {value} : {}), + itemSchema: { + type: 'tag', + label: '${name} ${text}' } - ], - valueField: 'name', - ...(value !== undefined ? {value} : {}), - itemSchema: { - type: 'tag', - label: '${name} ${text}' - } - }, - {}, - makeEnv({}) - ) - ).container; + }, + {}, + makeEnv({}) + ) + ).container; - const noValue = setup().querySelector('.cxd-MappingField .text-muted')! as HTMLElement; + const noValue = setup().querySelector( + '.cxd-MappingField .text-muted' + )! as HTMLElement; expect(noValue.innerHTML).toBe('-'); const value1 = setup(1).querySelector('.cxd-MappingField')! as HTMLElement; @@ -204,23 +226,24 @@ test('Renderer:mapping', async () => { }); test('Renderer:mapping html', async () => { - const setup = (value?: any) => render( - amisRender( - { - type: 'mapping', - map: { - 1: "漂亮", - 2: "开心", - 3: "惊吓", - 4: "紧张", - '*': '其他' + const setup = (value?: any) => + render( + amisRender( + { + type: 'mapping', + map: { + 1: "漂亮", + 2: "开心", + 3: "惊吓", + 4: "紧张", + '*': '其他' + }, + ...(value !== undefined ? {value} : {}) }, - ...(value !== undefined ? {value} : {}) - }, - {}, - makeEnv({}) - ) - ).container; + {}, + makeEnv({}) + ) + ).container; expect(setup()).toMatchSnapshot(); expect(setup(1)).toMatchSnapshot(); @@ -228,25 +251,28 @@ test('Renderer:mapping html', async () => { }); test('Renderer:mapping schema', async () => { - const setup = (value?: any) => render( - amisRender( - { - type: 'mapping', - map: { - 1: {type: 'tag', label: '漂亮'}, - 2: {type: 'tag', label: '开心'}, - 3: {type: 'tag', label: '惊吓'}, - 4: {type: 'tag', label: '紧张'}, - '*': {type: 'tag', label: '其他'} + const setup = (value?: any) => + render( + amisRender( + { + type: 'mapping', + map: { + 1: {type: 'tag', label: '漂亮'}, + 2: {type: 'tag', label: '开心'}, + 3: {type: 'tag', label: '惊吓'}, + 4: {type: 'tag', label: '紧张'}, + '*': {type: 'tag', label: '其他'} + }, + ...(value !== undefined ? {value} : {}) }, - ...(value !== undefined ? {value} : {}) - }, - {}, - makeEnv({}) - ) - ).container; + {}, + makeEnv({}) + ) + ).container; - const noValue = setup().querySelector('.cxd-MappingField .text-muted')! as HTMLElement; + const noValue = setup().querySelector( + '.cxd-MappingField .text-muted' + )! as HTMLElement; expect(noValue.innerHTML).toBe('-'); const value1 = setup(1).querySelector('.cxd-MappingField')! as HTMLElement; @@ -255,3 +281,68 @@ test('Renderer:mapping schema', async () => { const value5 = setup(5).querySelector('.cxd-MappingField')! as HTMLElement; expect(value5.innerHTML).toBe(tag('其他').innerHTML); }); + +// 对应 issue https://github.com/baidu/amis/issues/9613 +test('Renderer:mapping schema status', async () => { + const {container, getByText} = render( + amisRender( + { + type: 'page', + data: { + items: [ + { + status: 1, + id: 1 + } + ] + }, + body: [ + { + type: 'table', + title: '表格', + columns: [ + { + name: 'id', + label: 'ID' + }, + { + name: 'status', + label: '状态2', + type: 'mapping', + map: { + '*': { + type: 'status', + map: { + '0': 'schedule', + '1': 'rolling', + '2': 'success', + '3': 'fail', + '4': 'warning' + }, + labelMap: { + '2': '任务成功', + '1': '处理中', + '3': '异常终止', + '0': '等待中', + '4': '已过期' + } + } + } + } + ] + } + ] + }, + {}, + makeEnv({}) + ) + ); + + await wait(200); + + expect( + [].slice + .call(container.querySelectorAll('tbody td')) + .map((td: any) => td.textContent) + ).toEqual(['1', '处理中']); +}); diff --git a/packages/amis/src/renderers/Mapping.tsx b/packages/amis/src/renderers/Mapping.tsx index 027739d12..aacc70ba2 100644 --- a/packages/amis/src/renderers/Mapping.tsx +++ b/packages/amis/src/renderers/Mapping.tsx @@ -253,7 +253,7 @@ export const MappingField = withStore(props => } renderViewValue(value: any) { - const {render, itemSchema, data, labelField} = this.props; + const {render, itemSchema, data, labelField, name} = this.props; if (!itemSchema) { let label = value; @@ -265,6 +265,12 @@ export const MappingField = withStore(props => // object 也没有 type,不能作为schema渲染 // 默认取 label 字段 label = value['label']; + } else { + // 不会下发 value 了,所以要把 name 下发一下 + label = { + name, + ...label + }; } } else { label = value[labelField || 'label']; From 5072f74c345e6f95586da9ec752f1bba5380e8d9 Mon Sep 17 00:00:00 2001 From: wuduoyi Date: Sun, 25 Feb 2024 10:46:50 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E4=BA=8B=E4=BB=B6,=20=E6=94=AF=E6=8C=81=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E5=AE=B9=E5=99=A8=E7=BB=84=E4=BB=B6=20Closes=20#9475?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh-CN/concepts/event-action.md | 69 +++++++++++++++++ packages/amis-core/src/actions/PrintAction.ts | 51 +++++++++++++ packages/amis-core/src/actions/index.ts | 1 + packages/amis-core/src/renderers/Form.tsx | 6 +- packages/amis-core/src/utils/printElement.ts | 76 +++++++++++++++++++ packages/amis-ui/scss/components/_print.scss | 13 ++++ packages/amis-ui/scss/themes/_common.scss | 2 + packages/amis/src/renderers/CRUD.tsx | 5 +- packages/amis/src/renderers/CRUD2.tsx | 5 +- packages/amis/src/renderers/Container.tsx | 4 +- packages/amis/src/renderers/Flex.tsx | 7 +- packages/amis/src/renderers/Grid.tsx | 7 +- packages/amis/src/renderers/Grid2D.tsx | 11 ++- packages/amis/src/renderers/Table/index.tsx | 7 +- packages/amis/src/renderers/TableView.tsx | 5 +- packages/amis/src/renderers/Wrapper.tsx | 13 +++- 16 files changed, 265 insertions(+), 17 deletions(-) create mode 100644 packages/amis-core/src/actions/PrintAction.ts create mode 100644 packages/amis-core/src/utils/printElement.ts create mode 100644 packages/amis-ui/scss/components/_print.scss diff --git a/docs/zh-CN/concepts/event-action.md b/docs/zh-CN/concepts/event-action.md index 9141b9111..693f426c0 100644 --- a/docs/zh-CN/concepts/event-action.md +++ b/docs/zh-CN/concepts/event-action.md @@ -1444,6 +1444,75 @@ run action ajax | copyFormat | `string` | `text/html` | 复制格式 | | content | [模板](../../docs/concepts/template) | - | 指定复制的内容。可用 `${xxx}` 取值 | +### 打印 + +> 6.2.0 及以后版本 + +打印页面中的某个组件,对应的组件需要配置 `testid`,如果要打印多个,可以使用 `"testids": ["x", "y"]` 来打印多个组件 + +```schema +{ + type: 'page', + body: [ + { + type: 'button', + label: '打印', + level: 'primary', + className: 'mr-2', + onEvent: { + click: { + actions: [ + { + actionType: 'print', + args: { + testid: 'mycrud' + } + } + ] + } + } + }, + { + "type": "crud", + "api": "/api/mock2/sample", + "testid": "mycrud", + "syncLocation": false, + "columns": [ + { + "name": "id", + "label": "ID" + }, + { + "name": "engine", + "label": "Rendering engine" + }, + { + "name": "browser", + "label": "Browser" + }, + { + "name": "platform", + "label": "Platform(s)" + }, + { + "name": "version", + "label": "Engine version" + }, + { + "name": "grade", + "label": "CSS grade" + } + ] + } + ] +} +``` + +| 属性名 | 类型 | 默认值 | 说明 | +| ------- | ---------- | ------ | ----------------- | +| testid | `string` | | 组件的 testid | +| testids | `string[]` | - | 多个组件的 testid | + ### 发送邮件 通过配置`actionType: 'email'`和邮件属性实现发送邮件操作。 diff --git a/packages/amis-core/src/actions/PrintAction.ts b/packages/amis-core/src/actions/PrintAction.ts new file mode 100644 index 000000000..852776888 --- /dev/null +++ b/packages/amis-core/src/actions/PrintAction.ts @@ -0,0 +1,51 @@ +import {printElements} from '../utils/printElement'; +import {RendererEvent} from '../utils/renderer-event'; +import { + RendererAction, + ListenerAction, + ListenerContext, + registerAction +} from './Action'; + +export interface IPrintAction extends ListenerAction { + actionType: 'copy'; + args: { + testid?: string; + testids?: string[]; + }; +} + +/** + * 打印动作 + * + * @export + * @class PrintAction + * @implements {Action} + */ +export class PrintAction implements RendererAction { + async run( + action: IPrintAction, + renderer: ListenerContext, + event: RendererEvent + ) { + if (action.args?.testid) { + const element = document.querySelector( + `[data-testid='${action.args.testid}']` + ); + if (element) { + printElements([element]); + } + } else if (action.args?.testids) { + const elements: Element[] = []; + action.args.testids.forEach(testid => { + const element = document.querySelector(`[data-testid='${testid}']`); + if (element) { + elements.push(element); + } + }); + printElements(elements); + } + } +} + +registerAction('print', new PrintAction()); diff --git a/packages/amis-core/src/actions/index.ts b/packages/amis-core/src/actions/index.ts index 1276c0ddf..f4e99458a 100644 --- a/packages/amis-core/src/actions/index.ts +++ b/packages/amis-core/src/actions/index.ts @@ -19,5 +19,6 @@ import './EmailAction'; import './LinkAction'; import './ToastAction'; import './PageAction'; +import './PrintAction'; export * from './Action'; diff --git a/packages/amis-core/src/renderers/Form.tsx b/packages/amis-core/src/renderers/Form.tsx index 494dd9cd4..22b97c9e2 100644 --- a/packages/amis-core/src/renderers/Form.tsx +++ b/packages/amis-core/src/renderers/Form.tsx @@ -49,7 +49,7 @@ import LazyComponent from '../components/LazyComponent'; import {isAlive} from 'mobx-state-tree'; import type {LabelAlign} from './Item'; -import {injectObjectChain} from '../utils'; +import {buildTestId, injectObjectChain} from '../utils'; import {reaction} from 'mobx'; export interface FormHorizontal { @@ -1808,7 +1808,8 @@ export default class Form extends React.Component { render, staticClassName, static: isStatic = false, - loadingConfig + loadingConfig, + testid } = this.props; const {restError} = store; @@ -1840,6 +1841,7 @@ export default class Form extends React.Component { )} onSubmit={this.handleFormSubmit} noValidate + {...buildTestId(testid)} > {/* 实现回车自动提交 */} diff --git a/packages/amis-core/src/utils/printElement.ts b/packages/amis-core/src/utils/printElement.ts new file mode 100644 index 000000000..1045b9889 --- /dev/null +++ b/packages/amis-core/src/utils/printElement.ts @@ -0,0 +1,76 @@ +/** + * 打印元素,参考 https://github.com/szepeshazi/print-elements 里的实现 + * 原理就是遍历节点加上打印样式,然后打印完了再清理掉 + * 对代码做了改造和优化 + */ + +const hideFromPrintClass = 'pe-no-print'; +const preservePrintClass = 'pe-preserve-print'; +const preserveAncestorClass = 'pe-preserve-ancestor'; +const bodyElementName = 'BODY'; + +function hide(element: Element) { + if (!element.classList.contains(preservePrintClass)) { + element.classList.add(hideFromPrintClass); + } +} + +function preserve(element: Element, isStartingElement: boolean) { + element.classList.remove(hideFromPrintClass); + element.classList.add(preservePrintClass); + if (!isStartingElement) { + element.classList.add(preserveAncestorClass); + } +} + +function clean(element: Element) { + element.classList.remove(hideFromPrintClass); + element.classList.remove(preservePrintClass); + element.classList.remove(preserveAncestorClass); +} + +function walkSiblings(element: Element, callback: (element: Element) => void) { + let sibling = element.previousElementSibling; + while (sibling) { + callback(sibling); + sibling = sibling.previousElementSibling; + } + sibling = element.nextElementSibling; + while (sibling) { + callback(sibling); + sibling = sibling.nextElementSibling; + } +} + +function attachPrintClasses(element: Element, isStartingElement: boolean) { + preserve(element, isStartingElement); + walkSiblings(element, hide); +} + +function cleanup(element: Element, isStartingElement: boolean) { + clean(element); + walkSiblings(element, clean); +} + +function walkTree( + element: Element, + callback: (element: Element, isStartingElement: boolean) => void +) { + let currentElement: Element | null = element; + callback(currentElement, true); + currentElement = currentElement.parentElement; + while (currentElement && currentElement.nodeName !== bodyElementName) { + callback(currentElement, false); + currentElement = currentElement.parentElement; + } +} + +export function printElements(elements: Element[]) { + for (let i = 0; i < elements.length; i++) { + walkTree(elements[i], attachPrintClasses); + } + window.print(); + for (let i = 0; i < elements.length; i++) { + walkTree(elements[i], cleanup); + } +} diff --git a/packages/amis-ui/scss/components/_print.scss b/packages/amis-ui/scss/components/_print.scss new file mode 100644 index 000000000..092cdc90d --- /dev/null +++ b/packages/amis-ui/scss/components/_print.scss @@ -0,0 +1,13 @@ +@media print { + .pe-no-print { + display: none !important; + } + + .pe-preserve-ancestor { + display: block !important; + margin: 0 !important; + padding: 0 !important; + border: none !important; + box-shadow: none !important; + } +} diff --git a/packages/amis-ui/scss/themes/_common.scss b/packages/amis-ui/scss/themes/_common.scss index 1d09879b2..df33798ec 100644 --- a/packages/amis-ui/scss/themes/_common.scss +++ b/packages/amis-ui/scss/themes/_common.scss @@ -142,3 +142,5 @@ @import '../components/debug'; @import '../components/menu'; @import '../components/overflow-tpl'; + +@import '../components/print'; diff --git a/packages/amis/src/renderers/CRUD.tsx b/packages/amis/src/renderers/CRUD.tsx index 3b2f4feba..66daea731 100644 --- a/packages/amis/src/renderers/CRUD.tsx +++ b/packages/amis/src/renderers/CRUD.tsx @@ -7,7 +7,8 @@ import { RendererProps, evalExpressionWithConditionBuilder, filterTarget, - mapTree + mapTree, + buildTestId } from 'amis-core'; import {SchemaNode, Schema, ActionObject, PlainObject} from 'amis-core'; import {CRUDStore, ICRUDStore} from 'amis-core'; @@ -2516,6 +2517,7 @@ export default class CRUD extends React.Component { onSearchableFromInit, headerToolbarRender, footerToolbarRender, + testid, ...rest } = this.props; @@ -2526,6 +2528,7 @@ export default class CRUD extends React.Component { 'is-mobile': isMobile() })} style={style} + {...buildTestId(testid)} > {filter && (!store.filterTogggable || store.filterVisible) ? render( diff --git a/packages/amis/src/renderers/CRUD2.tsx b/packages/amis/src/renderers/CRUD2.tsx index 22a990520..118c526e2 100644 --- a/packages/amis/src/renderers/CRUD2.tsx +++ b/packages/amis/src/renderers/CRUD2.tsx @@ -30,7 +30,8 @@ import { isApiOutdated, isPureVariable, resolveVariableAndFilter, - parsePrimitiveQueryString + parsePrimitiveQueryString, + buildTestId } from 'amis-core'; import {Html, SpinnerExtraProps} from 'amis-ui'; import { @@ -1308,6 +1309,7 @@ export default class CRUD2 extends React.Component { columnsTogglable, headerToolbarClassName, footerToolbarClassName, + testid, ...rest } = this.props; @@ -1317,6 +1319,7 @@ export default class CRUD2 extends React.Component { 'is-loading': store.loading })} style={style} + {...buildTestId(testid)} >
{this.renderFilter(filterSchema)} diff --git a/packages/amis/src/renderers/Container.tsx b/packages/amis/src/renderers/Container.tsx index 933e71bd9..f26eee69c 100644 --- a/packages/amis/src/renderers/Container.tsx +++ b/packages/amis/src/renderers/Container.tsx @@ -195,7 +195,8 @@ export default class Container extends React.Component< wrapperCustomStyle, env, themeCss, - baseControlClassName + baseControlClassName, + testid } = this.props; const finalDraggable: boolean = isPureVariable(draggable) ? resolveVariableAndFilter(draggable, data, '| raw') @@ -231,6 +232,7 @@ export default class Container extends React.Component< onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} style={buildStyle(style, data)} + {...buildTestId(testid)} > {this.renderBody()} { wrapperCustomStyle, env, themeCss, - classnames: cx + classnames: cx, + testid } = this.props; const styleVar = buildStyle(style, data); const flexStyle = { @@ -150,6 +152,7 @@ export default class Flex extends React.Component { themeCss: wrapperCustomStyle }) )} + {...buildTestId(testid)} > {(Array.isArray(items) ? items : items ? [items] : []).map( (item, key) => diff --git a/packages/amis/src/renderers/Grid.tsx b/packages/amis/src/renderers/Grid.tsx index 28b754bb8..5c8875a48 100644 --- a/packages/amis/src/renderers/Grid.tsx +++ b/packages/amis/src/renderers/Grid.tsx @@ -5,7 +5,8 @@ import { RendererProps, buildStyle, CustomStyle, - setThemeClassName + setThemeClassName, + buildTestId } from 'amis-core'; import pick from 'lodash/pick'; import {BaseSchema, SchemaClassName, SchemaCollection} from '../Schema'; @@ -212,7 +213,8 @@ export default class Grid extends React.Component { id, wrapperCustomStyle, env, - themeCss + themeCss, + testid } = this.props; const styleVar = buildStyle(style, data); return ( @@ -239,6 +241,7 @@ export default class Grid extends React.Component { }) )} style={styleVar} + {...buildTestId(testid)} > {this.renderColumns(this.props.columns)} diff --git a/packages/amis/src/renderers/Grid2D.tsx b/packages/amis/src/renderers/Grid2D.tsx index f4f989b14..3dbe1845b 100644 --- a/packages/amis/src/renderers/Grid2D.tsx +++ b/packages/amis/src/renderers/Grid2D.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Renderer, RendererProps} from 'amis-core'; +import {buildTestId, Renderer, RendererProps} from 'amis-core'; import {Api, SchemaNode, Schema, ActionObject} from 'amis-core'; import {isVisible} from 'amis-core'; import {BaseSchema, SchemaObject} from '../Schema'; @@ -172,7 +172,8 @@ export default class Grid2D extends React.Component { } render() { - const {grids, cols, gap, gapRow, width, rowHeight, style} = this.props; + const {grids, cols, gap, gapRow, width, rowHeight, style, testid} = + this.props; const templateColumns = new Array(cols); templateColumns.fill('1fr'); @@ -214,7 +215,11 @@ export default class Grid2D extends React.Component { gridTemplateRows: templateRows.join(' ') }; - return
{this.renderGrids()}
; + return ( +
+ {this.renderGrids()} +
+ ); } } diff --git a/packages/amis/src/renderers/Table/index.tsx b/packages/amis/src/renderers/Table/index.tsx index c114fadfd..5ef1977c6 100644 --- a/packages/amis/src/renderers/Table/index.tsx +++ b/packages/amis/src/renderers/Table/index.tsx @@ -41,7 +41,8 @@ import { resizeSensor, offset, getStyleNumber, - getPropValue + getPropValue, + buildTestId } from 'amis-core'; import { Button, @@ -2802,7 +2803,8 @@ export default class Table extends React.Component { affixHeader, autoFillHeight, autoGenerateFilter, - mobileUI + mobileUI, + testid } = this.props; this.renderedToolbars = []; // 用来记录哪些 toolbar 已经渲染了,已经渲染了就不重复渲染了。 @@ -2821,6 +2823,7 @@ export default class Table extends React.Component { 'Table--autoFillHeight': autoFillHeight })} style={store.buildStyles(style)} + {...buildTestId(testid)} > {autoGenerateFilter ? this.renderAutoFilterForm() : null} {this.renderAffixHeader(tableClassName)} diff --git a/packages/amis/src/renderers/TableView.tsx b/packages/amis/src/renderers/TableView.tsx index 38bbf5bb7..bc1214b69 100644 --- a/packages/amis/src/renderers/TableView.tsx +++ b/packages/amis/src/renderers/TableView.tsx @@ -8,7 +8,8 @@ import { RendererProps, resolveMappingObject, CustomStyle, - setThemeClassName + setThemeClassName, + buildTestId } from 'amis-core'; import {BaseSchema, SchemaObject} from '../Schema'; @@ -276,6 +277,7 @@ export default class TableView extends React.Component { wrapperCustomStyle, env, themeCss, + testid, baseControlClassName } = this.props; @@ -298,6 +300,7 @@ export default class TableView extends React.Component { }) )} style={{width: width, borderCollapse: 'collapse'}} + {...buildTestId(testid)} > {this.renderCaption()} {this.renderCols()} diff --git a/packages/amis/src/renderers/Wrapper.tsx b/packages/amis/src/renderers/Wrapper.tsx index cb4c7ac88..b4fa79288 100644 --- a/packages/amis/src/renderers/Wrapper.tsx +++ b/packages/amis/src/renderers/Wrapper.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Renderer, RendererProps} from 'amis-core'; +import {buildTestId, Renderer, RendererProps} from 'amis-core'; import {BaseSchema, SchemaCollection} from '../Schema'; import {resolveVariable} from 'amis-core'; import {SchemaNode} from 'amis-core'; @@ -59,7 +59,15 @@ export default class Wrapper extends React.Component { } render() { - const {className, size, classnames: cx, style, data, wrap} = this.props; + const { + className, + size, + classnames: cx, + style, + data, + wrap, + testid + } = this.props; // 期望不要使用,给 form controls 用法自动转换时使用的。 if (wrap === false) { @@ -74,6 +82,7 @@ export default class Wrapper extends React.Component { className )} style={buildStyle(style, data)} + {...buildTestId(testid)} > {this.renderBody()}
From f67d3440994a6c431953729d50e43020645a579c Mon Sep 17 00:00:00 2001 From: wuduoyi Date: Tue, 27 Feb 2024 15:05:45 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis/src/renderers/Container.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/amis/src/renderers/Container.tsx b/packages/amis/src/renderers/Container.tsx index f26eee69c..2a8f70d1e 100644 --- a/packages/amis/src/renderers/Container.tsx +++ b/packages/amis/src/renderers/Container.tsx @@ -8,7 +8,8 @@ import { isPureVariable, resolveVariableAndFilter, CustomStyle, - setThemeClassName + setThemeClassName, + buildTestId } from 'amis-core'; import {DndContainer as DndWrapper} from 'amis-ui'; import {BaseSchema, SchemaClassName, SchemaCollection} from '../Schema'; From a5f32984a0d90244c9e79d262e5dced065f28a25 Mon Sep 17 00:00:00 2001 From: jinye Date: Tue, 27 Feb 2024 20:39:31 +0800 Subject: [PATCH 6/9] =?UTF-8?q?fix:=20editor-=E4=BF=AE=E5=A4=8D=E7=A9=BF?= =?UTF-8?q?=E6=A2=AD=E5=99=A8=E8=A1=A8=E6=A0=BC=E5=BD=A2=E5=BC=8F=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E8=A1=A8=E6=A0=BC=E5=88=97=E4=B8=8D=E5=A1=AB=E5=86=99?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=90=8D=E6=97=B6valueTpl=E4=B8=BAundefined?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../amis-editor/src/renderer/TransferTableControl.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/amis-editor/src/renderer/TransferTableControl.tsx b/packages/amis-editor/src/renderer/TransferTableControl.tsx index 638445f43..c5b87ceeb 100644 --- a/packages/amis-editor/src/renderer/TransferTableControl.tsx +++ b/packages/amis-editor/src/renderer/TransferTableControl.tsx @@ -324,7 +324,7 @@ export default class TransferTableOption extends React.Component< return { type: 'action', actionType: 'dialog', - label: '添加表格列', + label: '设置表格列', level: 'enhance', dialog: { title: '设置表格列选项', @@ -348,12 +348,14 @@ export default class TransferTableOption extends React.Component< { type: 'input-text', name: 'label', - placeholder: '标题' + placeholder: '标题', + required: true }, { type: 'input-text', name: 'name', - placeholder: '绑定字段名' + placeholder: '绑定字段名', + required: true }, { type: 'select', @@ -417,7 +419,7 @@ export default class TransferTableOption extends React.Component< { type: 'action', actionType: 'dialog', - label: '添加表格行', + label: '设置表格行', level: 'enhance', disabled: columns && columns.length === 0, block: true, From da910d58efeae5dc4a4d54e302df050ecac56efc Mon Sep 17 00:00:00 2001 From: shenmenga Date: Wed, 28 Feb 2024 11:44:03 +0800 Subject: [PATCH 7/9] =?UTF-8?q?fix:=20diff-editor=20=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis/src/renderers/Form/DiffEditor.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/amis/src/renderers/Form/DiffEditor.tsx b/packages/amis/src/renderers/Form/DiffEditor.tsx index 7eeb321ca..a17aa8998 100644 --- a/packages/amis/src/renderers/Form/DiffEditor.tsx +++ b/packages/amis/src/renderers/Form/DiffEditor.tsx @@ -73,7 +73,7 @@ function normalizeValue(value: any, language?: string) { export class DiffEditor extends React.Component { static defaultProps: Partial = { language: 'javascript', - theme: 'vs', + editorTheme: 'vs', options: { automaticLayout: false, selectOnLineNumbers: true, @@ -304,7 +304,7 @@ export class DiffEditor extends React.Component { size, options, language, - theme, + editorTheme, classnames: cx } = this.props; @@ -326,7 +326,7 @@ export class DiffEditor extends React.Component { onChange={onChange} disabled={disabled} language={language} - theme={theme} + editorTheme={editorTheme} editorDidMount={this.handleEditorMounted} editorFactory={this.editorFactory} options={{ From d1cfd3719f664efccbf97bd667ff6d4b2f033474 Mon Sep 17 00:00:00 2001 From: shenmenga Date: Wed, 28 Feb 2024 17:14:36 +0800 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20messages=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/components/CRUD/Table.jsx | 11 ++++++++++- packages/amis-core/src/store/service.ts | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/components/CRUD/Table.jsx b/examples/components/CRUD/Table.jsx index 6b6178d60..6fc8585c5 100644 --- a/examples/components/CRUD/Table.jsx +++ b/examples/components/CRUD/Table.jsx @@ -303,7 +303,16 @@ export default { body: { type: 'form', name: 'sample-edit-form', - api: '/api/sample/$id', + data:{ + env: 'test' + }, + api: { + method:'post', + url:'/api/sample/$id', + messages:{ + success: '成功了-${env}' + } + }, body: [ { type: 'input-text', diff --git a/packages/amis-core/src/store/service.ts b/packages/amis-core/src/store/service.ts index a5357be89..90c12009c 100644 --- a/packages/amis-core/src/store/service.ts +++ b/packages/amis-core/src/store/service.ts @@ -6,7 +6,7 @@ import {ServerError} from '../utils/errors'; import {normalizeApiResponseData} from '../utils/api'; import {replaceText} from '../utils/replaceText'; import {concatData} from '../utils/concatData'; - +import {filter} from 'amis-core'; export const ServiceStore = iRendererStore .named('ServiceStore') .props({ @@ -54,7 +54,7 @@ export const ServiceStore = iRendererStore } function updateMessage(msg?: string, error: boolean = false) { - self.msg = (msg && String(msg)) || ''; + self.msg = (msg && filter(msg, self.data)) || ''; self.error = error; } From f155bee8709b4a1ac71809cbfd40daf4ff98b11e Mon Sep 17 00:00:00 2001 From: liaoxuezhi <2betop.cn@gmail.com> Date: Thu, 29 Feb 2024 15:17:29 +0800 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=E7=BB=84=E4=BB=B6=E4=B8=AD?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E5=8A=A0=E8=BD=BD=E7=9A=84=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=20schema=20=E4=B9=9F=E6=94=AF=E6=8C=81=E5=A4=9A=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E8=A7=84=E5=88=99=20Close:=20#9564=20(#9610)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis-core/src/index.tsx | 3 +- packages/amis-core/src/store/service.ts | 2 + packages/amis-ui/scss/components/_app.scss | 5 + .../amis/__tests__/renderers/App.test.tsx | 104 ++++++++++++++++++ packages/amis/src/renderers/App.tsx | 14 ++- 5 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 packages/amis/__tests__/renderers/App.test.tsx diff --git a/packages/amis-core/src/index.tsx b/packages/amis-core/src/index.tsx index 723e83af6..ed12e589a 100644 --- a/packages/amis-core/src/index.tsx +++ b/packages/amis-core/src/index.tsx @@ -206,7 +206,8 @@ export { splitTarget, CustomStyle, enableDebug, - disableDebug + disableDebug, + envOverwrite }; export function render( diff --git a/packages/amis-core/src/store/service.ts b/packages/amis-core/src/store/service.ts index 90c12009c..1b5a6a8b5 100644 --- a/packages/amis-core/src/store/service.ts +++ b/packages/amis-core/src/store/service.ts @@ -6,6 +6,7 @@ import {ServerError} from '../utils/errors'; import {normalizeApiResponseData} from '../utils/api'; import {replaceText} from '../utils/replaceText'; import {concatData} from '../utils/concatData'; +import {envOverwrite} from '../envOverwrite'; import {filter} from 'amis-core'; export const ServiceStore = iRendererStore .named('ServiceStore') @@ -445,6 +446,7 @@ export const ServiceStore = iRendererStore } else { if (json.data) { const env = getEnv(self); + json.data = envOverwrite(json.data, env.locale); json.data = replaceText( json.data, env.replaceText, diff --git a/packages/amis-ui/scss/components/_app.scss b/packages/amis-ui/scss/components/_app.scss index eda5380f6..83e90d32d 100644 --- a/packages/amis-ui/scss/components/_app.scss +++ b/packages/amis-ui/scss/components/_app.scss @@ -2,11 +2,16 @@ height: px2rem(30px); line-height: px2rem(30px); font-size: px2rem(12px); + list-style: none; margin: 0; padding: 0 0 0 var(--gap-md); border-bottom: var(--borderWidth) solid var(--borderColor); + a { + font-size: inherit; + } + &-item { display: inline-block; } diff --git a/packages/amis/__tests__/renderers/App.test.tsx b/packages/amis/__tests__/renderers/App.test.tsx new file mode 100644 index 000000000..ea6f73152 --- /dev/null +++ b/packages/amis/__tests__/renderers/App.test.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import Action from '../../src/renderers/Action'; +import * as renderer from 'react-test-renderer'; +import { + render, + fireEvent, + cleanup, + screen, + waitFor, + within +} from '@testing-library/react'; +import {render as amisRender} from '../../src'; +import {makeEnv, wait} from '../helper'; +import '../../src'; + +afterEach(cleanup); + +// 关联 issue https://github.com/baidu/amis/issues/9564 +test('Renderers:App locale', async () => { + const fetcher = jest.fn().mockImplementation((api, options) => { + if (api.url.startsWith('/pageList')) { + return Promise.resolve({ + status: 200, + data: { + status: 0, + msg: '', + data: { + pages: [ + { + children: [ + { + 'label': 'Home', + 'icon': 'fa fa-home', + 'url': '/admin/page/home', + 'schemaApi': '/pageDetail', + 'isDefaultPage': true, + 'sort': 100, + 'zh-CN': { + label: '首页' + } + } + ] + } + ] + } + } + }); + } else if (api.url.startsWith('/pageDetail')) { + return Promise.resolve({ + status: 200, + data: { + type: 'page', + body: [ + { + 'type': 'input-text', + 'name': 'a', + 'label': 'dev', + 'content': 'False', + 'zh-CN': { + label: '开发环境' + } + } + ] + } + }); + } + + return Promise.resolve({ + status: 200, + data: { + status: 404, + msg: 'notFound' + } + }); + }); + + const {container, getByText} = render( + amisRender( + { + type: 'app', + api: { + method: 'get', + url: '/pageList' + } + }, + { + locale: 'zh-CN' + }, + makeEnv({ + fetcher + }) + ) + ); + + await wait(500); + + const link = container.querySelector('nav li span'); + expect(link).toBeInTheDocument(); + expect(link!.textContent).toBe('首页'); + + const inputLabel = container.querySelector('.cxd-Form-label'); + expect(inputLabel).toBeInTheDocument(); + expect(inputLabel!.textContent).toBe('开发环境'); +}); diff --git a/packages/amis/src/renderers/App.tsx b/packages/amis/src/renderers/App.tsx index 5acbed370..0c62c54b2 100644 --- a/packages/amis/src/renderers/App.tsx +++ b/packages/amis/src/renderers/App.tsx @@ -8,7 +8,13 @@ import { SpinnerExtraProps } from 'amis-ui'; import {Layout} from 'amis-ui'; -import {Renderer, RendererProps, filter, replaceText} from 'amis-core'; +import { + Renderer, + RendererProps, + envOverwrite, + filter, + replaceText +} from 'amis-core'; import { BaseSchema, SchemaApi, @@ -244,11 +250,13 @@ export default class App extends React.Component { store, env, showFullBreadcrumbPath = false, - showBreadcrumbHomePath = true + showBreadcrumbHomePath = true, + locale } = this.props; if (isEffectiveApi(api, store.data)) { const json = await store.fetchInitData(api, store.data, {}); + if (env.replaceText) { json.data = replaceText( json.data, @@ -258,6 +266,8 @@ export default class App extends React.Component { } if (json?.data.pages) { + json.data = envOverwrite(json.data, locale); + store.setPages(json.data.pages); store.updateActivePage( Object.assign({}, env ?? {}, {