Merge branch 'master' into fix-ref

This commit is contained in:
liaoxuezhi 2024-02-29 15:42:20 +08:00 committed by GitHub
commit 132f47cbe9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 1184 additions and 248 deletions

View File

@ -7,7 +7,7 @@
[文档(国外)](https://baidu.github.io/amis/) |
[可视化编辑器](https://aisuda.github.io/amis-editor-demo/) |
[amis-admin](https://github.com/aisuda/amis-admin) |
[爱速搭](https://aisuda.bce.baidu.com/aisuda-docs/)
[爱速搭](https://aisuda.baidu.com/)
</div>
@ -112,4 +112,4 @@ npm run release
## 低代码平台
amis 只能实现前端低代码,如果需要完整的低代码平台推荐使用[爱速搭](https://aisuda.bce.baidu.com/aisuda-docs/)。
amis 只能实现前端低代码,如果需要完整的低代码平台推荐使用[爱速搭](https://aisuda.baidu.com/)。

View File

@ -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'`和邮件属性实现发送邮件操作。

View File

@ -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',

View File

@ -11,6 +11,7 @@ import {makeEnv} from './helper';
test('Scoped splitTarget', async () => {
expect(splitTarget('abc')).toEqual(['abc']);
expect(splitTarget('a,b,c')).toEqual(['a', 'b', 'c']);
expect(splitTarget('a, b, c ')).toEqual(['a', 'b', 'c']);
expect(splitTarget('a?x=1&y=2,b,c')).toEqual(['a?x=1&y=2', 'b', 'c']);
expect(splitTarget('a?x=${[a, b]},b,c')).toEqual(['a?x=${[a, b]}', 'b', 'c']);
expect(splitTarget('a?x=${[a, b]}')).toEqual(['a?x=${[a, b]}']);

View File

@ -68,7 +68,7 @@ export function splitTarget(target: string): Array<string> {
let parts: Array<string> = [];
pos.reduceRight((arr: Array<string>, index) => {
arr.unshift(target.slice(index + 1));
arr.unshift(target.slice(index + 1)?.trim());
target = target.slice(0, index);
return arr;
}, parts);

View File

@ -267,7 +267,7 @@ export function HocStoreFactory(renderer: {
props.store?.storeType === 'ComboStore'
? undefined
: syncDataFromSuper(
store.data,
{...store.data, ...props.data},
(props.data as any).__super,
(prevProps.data as any).__super,
store,

View File

@ -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<any>
) {
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());

View File

@ -19,5 +19,6 @@ import './EmailAction';
import './LinkAction';
import './ToastAction';
import './PageAction';
import './PrintAction';
export * from './Action';

View File

@ -206,7 +206,8 @@ export {
splitTarget,
CustomStyle,
enableDebug,
disableDebug
disableDebug,
envOverwrite
};
export function render(

View File

@ -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<FormProps, object> {
render,
staticClassName,
static: isStatic = false,
loadingConfig
loadingConfig,
testid
} = this.props;
const {restError} = store;
@ -1840,6 +1841,7 @@ export default class Form extends React.Component<FormProps, object> {
)}
onSubmit={this.handleFormSubmit}
noValidate
{...buildTestId(testid)}
>
{/* 实现回车自动提交 */}
<input type="submit" style={{display: 'none'}} />

View File

@ -468,7 +468,7 @@ export function wrapControl<
setInitialValue(value: any) {
const model = this.model!;
const {formStore: form, data} = this.props;
const {formStore: form, data, canAccessSuperData} = this.props;
const isExp = isExpression(value);
if (isExp) {
@ -479,10 +479,22 @@ export function wrapControl<
} else {
let initialValue = model.extraName
? [
getVariable(data, model.name, form?.canAccessSuperData),
getVariable(data, model.extraName, form?.canAccessSuperData)
getVariable(
data,
model.name,
canAccessSuperData ?? form?.canAccessSuperData
),
getVariable(
data,
model.extraName,
canAccessSuperData ?? form?.canAccessSuperData
)
]
: getVariable(data, model.name, form?.canAccessSuperData);
: getVariable(
data,
model.name,
canAccessSuperData ?? form?.canAccessSuperData
);
if (
model.extraName &&

View File

@ -6,7 +6,8 @@ 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')
.props({
@ -54,7 +55,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;
}
@ -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,

View File

@ -487,6 +487,7 @@ export const TableStore = iRendererStore
.named('TableStore')
.props({
columns: types.array(Column),
columnsKey: '',
rows: types.array(Row),
selectedRows: types.array(types.reference(Row)),
expandedRows: types.array(types.string),
@ -1105,6 +1106,7 @@ export const TableStore = iRendererStore
(self.tableLayout = config.tableLayout);
if (config.columns && Array.isArray(config.columns)) {
self.columnsKey = getPersistDataKey(config.columns);
let columns: Array<SColumn> = config.columns
.map(column => {
if (
@ -1123,7 +1125,7 @@ export const TableStore = iRendererStore
.filter(column => column);
// 更新列顺序afterCreate生命周期中更新columns不会触发组件的render
const key = getPersistDataKey(columns);
const key = self.columnsKey;
const data = localStorage.getItem(key);
let tableMetaData = null;
@ -1286,7 +1288,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 可能需要让修改过列宽的保持相应宽度,目前这样相当于重置了
}`;
});
@ -1841,7 +1843,7 @@ export const TableStore = iRendererStore
*
*/
function persistSaveToggledColumns() {
const key = getPersistDataKey(self.columnsData);
const key = self.columnsKey;
localStorage.setItem(
key,
@ -1947,7 +1949,7 @@ export const TableStore = iRendererStore
if (!isAlive(self)) {
return;
}
const key = getPersistDataKey(self.columnsData);
const key = self.columnsKey;
const data = localStorage.getItem(key);
if (data) {

View File

@ -368,7 +368,7 @@ export function responseAdaptor(ret: fetcherResult, api: ApiObject) {
// 返回内容是 string说明 content-type 不是 json这时可能是返回了纯文本或 html
if (typeof data === 'string') {
const contentType = (ret.headers as any)['content-type'] || '';
const contentType = (ret.headers as any)?.['content-type'] || '';
// 如果是文本类型就尝试解析一下
if (
ret.headers &&

View File

@ -38,7 +38,7 @@ export function attachmentAdpator(
}
}
let type = response.headers['content-type'];
let type = response.headers?.['content-type'];
let blob =
response.data.toString() === '[object Blob]'
? response.data

View File

@ -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;

View File

@ -169,7 +169,7 @@ export function calculatePosition(
: overlayHeight / 2;
// 如果还有其他可选项,则做位置判断,是否在可视区域,不完全在则继续看其他定位情况。
if (tests.length) {
if (tests.length || isAuto) {
const transformed = {
x: clip.x + positionLeft / scaleX,
y: clip.y + positionTop / scaleY,
@ -200,6 +200,10 @@ export function calculatePosition(
if (visibleX && visibleY) {
break;
} else if (isAuto && tests.length === 0) {
// 如果是 auto 模式,且最后一个方向都不可见,则直接平移到可见区域
visibleY || (positionTop = window.innerHeight - transformed.height);
visibleX || (positionLeft = window.innerWidth - transformed.width);
}
}
}

View File

@ -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);
}
}

View File

@ -53,6 +53,7 @@ export default class MiniEditor extends Editor {
theme={theme}
data={data}
autoFocus={autoFocus}
appLocale={this.props.appLocale}
></Preview>
</div>
</div>

View File

@ -812,14 +812,19 @@ export class CRUDPlugin extends BasePlugin {
api: valueSchema.api?.method?.match(/^(post|put)$/i)
? valueSchema.api
: {...valueSchema.api, method: 'post'},
body: valueSchema.columns.map((column: ColumnItem) => {
const type = column.type;
return {
type: viewTypeToEditType(type),
name: column.name,
label: column.label
};
})
body: valueSchema.columns
.filter(
({type}: any) =>
type !== 'progress' && type !== 'operation'
)
.map((column: ColumnItem) => {
const type = column.type;
return {
type: viewTypeToEditType(type),
name: column.name,
label: column.label
};
})
};
valueSchema.headerToolbar = [createSchemaBase, 'bulkActions'];
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -142,3 +142,5 @@
@import '../components/debug';
@import '../components/menu';
@import '../components/overflow-tpl';
@import '../components/print';

View File

@ -0,0 +1,85 @@
import {fireEvent, render} from '@testing-library/react';
import '../../../src';
import {render as amisRender} from '../../../src';
import {makeEnv, wait} from '../../helper';
test('paginationWrapper: service + crud', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
data: {
items: [
{
label: '110101',
name: '东城区',
sale: 46861
},
{
label: '110102',
name: '西城区',
sale: 44882
}
]
}
}
})
);
const {container} = render(
amisRender(
{
type: 'page',
body: [
{
type: 'service',
id: 'u:ff652047d747',
api: {
method: 'get',
url: 'https://yapi.baidu-int.com/mock/42601/amis-chart/chart/sales/data2'
},
body: [
{
type: 'pagination-wrapper',
body: [
{
type: 'crud',
source: '${items}',
columns: [
{
name: 'label',
label: '地区',
type: 'text',
id: 'u:331ab3342710'
},
{
name: 'sale',
label: '销售',
type: 'text',
id: 'u:3dba120eda1d'
}
],
id: 'u:b3c77cb44fc8',
perPageAvailable: [10]
}
],
inputName: 'items',
outputName: 'items',
perPage: 20,
position: 'bottom'
}
]
}
]
},
{},
makeEnv({
fetcher
})
)
);
await wait(200);
const tds = [].slice
.call(container.querySelectorAll('tbody td'))
.map((td: any) => td.textContent);
expect(tds).toEqual(['110101', '46861', '110102', '44882']);
});

View File

@ -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('开发环境');
});

View File

@ -291,7 +291,7 @@ exports[`Renderer:inputCity with searchable: open select 1`] = `
<div
class="cxd-PopOver cxd-Select-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; width: auto; left: 0px; top: 0px; position: relative;"
style="display: block; width: auto; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div

View File

@ -448,7 +448,7 @@ exports[`Renderer:input table add 1`] = `
<th
class="is-sticky is-sticky-right is-sticky-first-right cxd-Table-operationCell"
data-index="5"
style="right: 0px;"
style="width: 150px; min-width: 150px;"
>
<div
class="cxd-TableCell--title v-middle nowrap"

View File

@ -587,7 +587,7 @@ exports[`Renderer:InputTag InputTag with options 1`] = `
<div
class="cxd-PopOver cxd-TagControl-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; left: 0px; top: 0px; position: relative;"
style="display: block; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div

View File

@ -209,7 +209,7 @@ exports[`Renderer:repeat 1`] = `
<div
class="cxd-PopOver cxd-Select-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; width: 0px; left: 0px; top: 0px; position: relative;"
style="display: block; width: 0px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div

View File

@ -356,7 +356,7 @@ exports[`Renderer:select associated mode with virtual 1`] = `
<div
class="cxd-PopOver cxd-TransferDropDown-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; min-width: 100px; left: 0px; top: 0px; position: relative;"
style="display: block; min-width: 100px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div
@ -970,7 +970,7 @@ exports[`Renderer:select chained mode with virtual 1`] = `
<div
class="cxd-PopOver cxd-TransferDropDown-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; min-width: 100px; left: 0px; top: 0px; position: relative;"
style="display: block; min-width: 100px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div
@ -1516,7 +1516,7 @@ exports[`Renderer:select group mode with virtual 1`] = `
<div
class="cxd-PopOver cxd-TransferDropDown-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; min-width: 100px; left: 0px; top: 0px; position: relative;"
style="display: block; min-width: 100px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div
@ -2084,7 +2084,7 @@ exports[`Renderer:select table mode with virtual 1`] = `
<div
class="cxd-PopOver cxd-TransferDropDown-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; min-width: 100px; left: 0px; top: 0px; position: relative;"
style="display: block; min-width: 100px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div
@ -2458,7 +2458,7 @@ exports[`Renderer:select table with labelField & valueField 1`] = `
<div
class="cxd-PopOver cxd-TransferDropDown-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; min-width: 100px; left: 0px; top: 0px; position: relative;"
style="display: block; min-width: 100px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div
@ -2956,7 +2956,7 @@ exports[`Renderer:select virtual 1`] = `
<div
class="cxd-PopOver cxd-Select-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; width: auto; left: 0px; top: 0px; position: relative;"
style="display: block; width: auto; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div

View File

@ -1579,7 +1579,7 @@ exports[`Renderer:text with options and multiple Renderer:text with options and
<div
class="cxd-PopOver cxd-TextControl-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; width: 0px; left: 0px; top: 0px; position: relative;"
style="display: block; width: 0px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div
@ -1811,7 +1811,7 @@ exports[`Renderer:text with options and multiple Renderer:text with options and
<div
class="cxd-PopOver cxd-TextControl-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; width: 0px; left: 0px; top: 0px; position: relative;"
style="display: block; width: 0px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div
@ -2070,7 +2070,7 @@ exports[`Renderer:text with options and multiple Renderer:text with options and
<div
class="cxd-PopOver cxd-TextControl-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; width: 0px; left: 0px; top: 0px; position: relative;"
style="display: block; width: 0px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div
@ -2328,7 +2328,7 @@ exports[`Renderer:text with options and multiple Renderer:text with options and
<div
class="cxd-PopOver cxd-TextControl-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; width: 0px; left: 0px; top: 0px; position: relative;"
style="display: block; width: 0px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div
@ -2699,7 +2699,7 @@ exports[`Renderer:text with options: options is open 1`] = `
<div
class="cxd-PopOver cxd-TextControl-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; width: 0px; left: 0px; top: 0px; position: relative;"
style="display: block; width: 0px; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div

View File

@ -810,6 +810,218 @@ 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'
}
]
});
});
// 对应 github issue: https://github.com/baidu/amis/issues/9520
test('Renderer:input-table canAccessSuperData', async () => {
const onSubmit = jest.fn();
const {container} = render(
amisRender(
{
type: 'page',
body: {
type: 'form',
data: {
a: 'xxx',
table: [
{
a: 'a1',
b: 'b1'
}
]
},
api: '/amis/api/mock2/form/saveForm',
body: [
{
showIndex: true,
type: 'input-table',
name: 'table',
addable: true,
needConfirm: true,
columns: [
{
name: 'a',
label: 'A',
type: 'wrapper',
body: [
{
name: 'a',
label: false,
type: 'input-text'
}
]
},
{
name: 'b',
label: 'B',
type: 'input-text'
}
]
}
]
}
},
{},
makeEnv({})
)
);
await wait(200);
const addBtn = container.querySelector('.cxd-OperationField button');
expect(addBtn).toBeInTheDocument();
fireEvent.click(addBtn!);
await wait(200);
const confrimBtn = container.querySelector('.cxd-OperationField button');
expect(confrimBtn).toBeInTheDocument();
fireEvent.click(confrimBtn!);
await wait(200);
const inputs = [].slice
.call(container.querySelectorAll('tbody td input[name="a"]'))
.map((td: any) => td.value);
expect(inputs).toEqual(['a1', '']);
});
// 对应 github issue: https://github.com/baidu/amis/issues/9537
test('Renderer:input-table item confirm validate', async () => {
const onSubmit = jest.fn();
@ -820,6 +1032,7 @@ test('Renderer:input-table item confirm validate', async () => {
body: {
type: 'form',
data: {
a: 'xxx',
table: [
{
a: 'a1',

View File

@ -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: "<span class='label label-info'>漂亮</span>",
2: "<span class='label label-success'>开心</span>",
3: "<span class='label label-danger'>惊吓</span>",
4: "<span class='label label-warning'>紧张</span>",
'*': '其他'
const setup = (value?: any) =>
render(
amisRender(
{
type: 'mapping',
map: {
1: "<span class='label label-info'>漂亮</span>",
2: "<span class='label label-success'>开心</span>",
3: "<span class='label label-danger'>惊吓</span>",
4: "<span class='label label-warning'>紧张</span>",
'*': '其他'
},
...(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', '处理中']);
});

View File

@ -1125,6 +1125,109 @@ test('Renderer:table-accessSuperData4', () => {
expect(td2?.textContent).toBe('-');
});
// https://github.com/baidu/amis/issues/9556
test('Renderer:table-accessSuperData5', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'page',
data: {
engine: 'xxx',
items: [
{
id: 1
},
{
id: 2,
engine: 'Trident'
}
]
},
body: {
type: 'table',
name: 'crud',
source: '${items}',
columns: [
{
name: 'id',
label: 'ID'
},
{
type: 'static-text',
name: 'engine',
label: 'Rendering engine'
},
{
type: 'text',
name: 'engine',
label: 'Rendering engine'
}
]
}
},
{},
makeEnv({})
)
);
await wait(200);
const tds = [].slice
.call(container.querySelectorAll('td'))
.map((td: any) => td.textContent);
expect(tds).toEqual(['1', '-', '-', '2', 'Trident', 'Trident']);
});
test('Renderer:table-accessSuperData6', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'page',
data: {
engine: 'xxx',
items: [
{
id: 1
},
{
id: 2,
engine: 'Trident'
}
]
},
body: {
type: 'table',
name: 'crud',
source: '${items}',
columns: [
{
name: 'id',
label: 'ID'
},
{
type: 'static-text',
name: 'engine',
label: 'Rendering engine',
canAccessSuperData: true
},
{
type: 'text',
name: 'engine',
label: 'Rendering engine'
}
]
}
},
{},
makeEnv({})
)
);
await wait(200);
const tds = [].slice
.call(container.querySelectorAll('td'))
.map((td: any) => td.textContent);
expect(tds).toEqual(['1', 'xxx', '-', '2', 'Trident', 'Trident']);
});
test('Renderer:table-each', () => {
const {container, getByText} = render(
amisRender(

View File

@ -903,7 +903,7 @@ exports[`Renderer:Pagination with showPerPage & perPageAvailable & showPageInput
<div
class="cxd-PopOver cxd-Select-popover cxd-PopOver--leftBottomLeftTop cxd-PopOver--v-top"
role="popover"
style="display: block; width: auto; left: 0px; top: 0px; position: relative;"
style="display: block; width: auto; left: 1024px; top: 768px; position: relative;"
theme="cxd"
>
<div

View File

@ -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<AppProps, object> {
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<AppProps, object> {
}
if (json?.data.pages) {
json.data = envOverwrite(json.data, locale);
store.setPages(json.data.pages);
store.updateActivePage(
Object.assign({}, env ?? {}, {

View File

@ -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<CRUDProps, any> {
onSearchableFromInit,
headerToolbarRender,
footerToolbarRender,
testid,
...rest
} = this.props;
@ -2526,6 +2528,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
'is-mobile': isMobile()
})}
style={style}
{...buildTestId(testid)}
>
{filter && (!store.filterTogggable || store.filterVisible)
? render(

View File

@ -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<CRUD2Props, any> {
columnsTogglable,
headerToolbarClassName,
footerToolbarClassName,
testid,
...rest
} = this.props;
@ -1317,6 +1319,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
'is-loading': store.loading
})}
style={style}
{...buildTestId(testid)}
>
<div className={cx('Crud2-filter')}>
{this.renderFilter(filterSchema)}

View File

@ -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';
@ -195,7 +196,8 @@ export default class Container<T> extends React.Component<
wrapperCustomStyle,
env,
themeCss,
baseControlClassName
baseControlClassName,
testid
} = this.props;
const finalDraggable: boolean = isPureVariable(draggable)
? resolveVariableAndFilter(draggable, data, '| raw')
@ -231,6 +233,7 @@ export default class Container<T> extends React.Component<
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={buildStyle(style, data)}
{...buildTestId(testid)}
>
{this.renderBody()}
<CustomStyle

View File

@ -8,7 +8,8 @@ import {
Renderer,
RendererProps,
CustomStyle,
setThemeClassName
setThemeClassName,
buildTestId
} from 'amis-core';
import {Schema} from 'amis-core';
import {BaseSchema, SchemaCollection, SchemaObject} from '../Schema';
@ -111,7 +112,8 @@ export default class Flex extends React.Component<FlexProps, object> {
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<FlexProps, object> {
themeCss: wrapperCustomStyle
})
)}
{...buildTestId(testid)}
>
{(Array.isArray(items) ? items : items ? [items] : []).map(
(item, key) =>

View File

@ -73,7 +73,7 @@ function normalizeValue(value: any, language?: string) {
export class DiffEditor extends React.Component<DiffEditorProps, any> {
static defaultProps: Partial<DiffEditorProps> = {
language: 'javascript',
theme: 'vs',
editorTheme: 'vs',
options: {
automaticLayout: false,
selectOnLineNumbers: true,
@ -304,7 +304,7 @@ export class DiffEditor extends React.Component<DiffEditorProps, any> {
size,
options,
language,
theme,
editorTheme,
classnames: cx
} = this.props;
@ -326,7 +326,7 @@ export class DiffEditor extends React.Component<DiffEditorProps, any> {
onChange={onChange}
disabled={disabled}
language={language}
theme={theme}
editorTheme={editorTheme}
editorDidMount={this.handleEditorMounted}
editorFactory={this.editorFactory}
options={{

View File

@ -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<T> extends React.Component<GridProps & T, object> {
id,
wrapperCustomStyle,
env,
themeCss
themeCss,
testid
} = this.props;
const styleVar = buildStyle(style, data);
return (
@ -239,6 +241,7 @@ export default class Grid<T> extends React.Component<GridProps & T, object> {
})
)}
style={styleVar}
{...buildTestId(testid)}
>
{this.renderColumns(this.props.columns)}
<Spinner loadingConfig={loadingConfig} overlay show={loading} />

View File

@ -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<Grid2DProps, object> {
}
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<Grid2DProps, object> {
gridTemplateRows: templateRows.join(' ')
};
return <div style={curStyle}>{this.renderGrids()}</div>;
return (
<div style={curStyle} {...buildTestId(testid)}>
{this.renderGrids()}
</div>
);
}
}

View File

@ -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'];

View File

@ -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,

View File

@ -191,7 +191,8 @@ export function AutoFilterForm({
autoGenerateFilter,
activedSearchableColumns,
searchableColumns,
searchFormExpanded
searchFormExpanded,
__ // 保证语言更新后能重新渲染
]);
return render('searchable-form', schema, {

View File

@ -1,5 +1,10 @@
import React from 'react';
import {chromeVersion, type IColumn, type ITableStore} from 'amis-core';
import {
chromeVersion,
isSafari,
type IColumn,
type ITableStore
} from 'amis-core';
import {observer} from 'mobx-react';
export function ColGroup({
@ -37,7 +42,9 @@ export function ColGroup({
// 低版本同时设置 thead>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(

View File

@ -41,7 +41,8 @@ import {
resizeSensor,
offset,
getStyleNumber,
getPropValue
getPropValue,
buildTestId
} from 'amis-core';
import {
Button,
@ -1301,8 +1302,8 @@ export default class Table extends React.Component<TableProps, object> {
if (this.resizeLine) {
return;
}
this.props.store.syncTableWidth();
this.props.store.initTableWidth();
this.props.store.syncTableWidth();
this.handleOutterScroll();
callback && setTimeout(callback, 20);
}
@ -1662,6 +1663,7 @@ export default class Table extends React.Component<TableProps, object> {
const {store} = this.props;
store.updateColumns(columns);
store.persistSaveToggledColumns();
}
renderAutoFilterForm(): React.ReactNode {
@ -2802,7 +2804,8 @@ export default class Table extends React.Component<TableProps, object> {
affixHeader,
autoFillHeight,
autoGenerateFilter,
mobileUI
mobileUI,
testid
} = this.props;
this.renderedToolbars = []; // 用来记录哪些 toolbar 已经渲染了,已经渲染了就不重复渲染了。
@ -2821,6 +2824,7 @@ export default class Table extends React.Component<TableProps, object> {
'Table--autoFillHeight': autoFillHeight
})}
style={store.buildStyles(style)}
{...buildTestId(testid)}
>
{autoGenerateFilter ? this.renderAutoFilterForm() : null}
{this.renderAffixHeader(tableClassName)}

View File

@ -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<TableViewProps, object> {
wrapperCustomStyle,
env,
themeCss,
testid,
baseControlClassName
} = this.props;
@ -298,6 +300,7 @@ export default class TableView extends React.Component<TableViewProps, object> {
})
)}
style={{width: width, borderCollapse: 'collapse'}}
{...buildTestId(testid)}
>
{this.renderCaption()}
{this.renderCols()}

View File

@ -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<WrapperProps, object> {
}
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<WrapperProps, object> {
className
)}
style={buildStyle(style, data)}
{...buildTestId(testid)}
>
{this.renderBody()}
</div>

View File

@ -1,7 +1,7 @@
db
__pycache__
text.pickle
embedding.pickle
text.json
embedding.json
.env
m3e-base
flagged

View File

@ -2,7 +2,7 @@ import sys
import os
import glob
import uuid
import pickle
import json
from embedding import get_embedding
from split_markdown import split_markdown
from vector_store import get_client
@ -21,11 +21,11 @@ text_blocks_by_id = {}
embedding_cache = {}
embedding_cache_file = os.path.join(
os.path.dirname(__file__), 'embedding.pickle')
os.path.dirname(__file__), 'embedding.json')
if os.path.exists(embedding_cache_file):
with open(embedding_cache_file, 'rb') as f:
embedding_cache = pickle.load(f)
embedding_cache = json.load(f)
def get_embedding_with_cache(text):
@ -65,8 +65,8 @@ for filename in glob.iglob(doc_dir + '**/*.md', recursive=True):
)
with open(os.path.join(os.path.dirname(__file__), 'text.pickle'), 'wb') as f:
pickle.dump(text_blocks_by_id, f, pickle.HIGHEST_PROTOCOL)
with open(os.path.join(os.path.dirname(__file__), 'text.json'), 'w') as f:
json.dump(text_blocks_by_id, f)
with open(embedding_cache_file, 'wb') as f:
pickle.dump(embedding_cache, f, pickle.HIGHEST_PROTOCOL)
with open(embedding_cache_file, 'w') as f:
json.dump(embedding_cache, f)

View File

@ -3,7 +3,7 @@ from split_markdown import split_markdown
from embedding import get_embedding
import gradio as gr
import os
import pickle
import json
from llm.wenxin import Wenxin, ModelName
from dotenv import load_dotenv
load_dotenv()
@ -15,8 +15,8 @@ collection = chroma_client.get_collection(name="amis")
wenxin = Wenxin()
text_blocks_by_id = {}
with open(os.path.join(os.path.dirname(__file__), 'text.pickle'), 'rb') as f:
text_blocks_by_id = pickle.load(f)
with open(os.path.join(os.path.dirname(__file__), 'text.json'), 'rb') as f:
text_blocks_by_id = json.load(f)
def get_prompt(context, query):