Merge branch 'dev' into release

This commit is contained in:
Xu Chang 2024-03-15 11:10:48 +08:00
commit 22baf22456
31 changed files with 151 additions and 67 deletions

View File

@ -1,9 +1,9 @@
export default OakComponent({
isList: false,
data: {
slideWidth: 0,
slideLeft: 0,
slideShow: false,
slideWidth: 0, //小程序使用
slideLeft: 0, //小程序使用
slideShow: false, //小程序使用
commonAction: [
'create',
'update',

View File

@ -63,7 +63,7 @@ export default OakComponent({
let currentUrl = event.currentTarget.dataset.src;
let urlList = event.currentTarget.dataset.list;
wx.previewImage({
current: currentUrl,
current: currentUrl, // 当前显示图片的http链接
urls: urlList, // 需要预览的图片http链接列表
});
},

View File

@ -37,7 +37,7 @@ export default OakComponent({
code: '',
title: '',
desc: '',
icon: '',
icon: '', //web独有
imagePath: '', //小程序独有
},
lifetimes: {

View File

@ -90,7 +90,7 @@ export default OakComponent({
attribute: {},
options: [],
inputType: '',
timeStartStr: '',
timeStartStr: '', // 小程序选择时间显示
timeEndStr: '',
selectedLabel: '',
minDateMp: new Date(1980, 1, 1).getTime(),

View File

@ -9,7 +9,7 @@ type ToolBarProps = {
title?: React.ReactNode;
buttonGroup?: buttonProps[];
extraContent?: React.ReactNode;
reload: () => void;
reload?: () => void;
};
declare function ToolBar(props: ToolBarProps): React.JSX.Element;
export default ToolBar;

View File

@ -14,13 +14,14 @@ function ToolBar(props) {
<Space align='center'>
{extraContent}
{buttonGroup && buttonGroup.length > 0 && (<ButtonGroup items={buttonGroup}/>)}
<Tooltip title={features.locales.t('reload')}>
<div className={Style.reloadIconBox} onClick={() => {
reload();
}}>
<ReloadOutlined />
</div>
</Tooltip>
{reload &&
<Tooltip title={features.locales.t('reload')}>
<div className={Style.reloadIconBox} onClick={() => {
reload();
}}>
<ReloadOutlined />
</div>
</Tooltip>}
<ColumnSetting />
</Space>
</div>

View File

@ -68,9 +68,7 @@ const ProList = (props) => {
},
}}>
<div className={Style.container}>
{!isMobile && !hideDefaultButtons && (<ToolBar title={title} extraContent={extraContent} buttonGroup={buttonGroup} reload={() => {
onReload && onReload();
}}/>)}
{!isMobile && !hideDefaultButtons && (<ToolBar title={title} extraContent={extraContent} buttonGroup={buttonGroup} reload={onReload}/>)}
{isMobile && <ButtonGroup items={buttonGroup}/>}
<List entity={entity} extraActions={extraActions} onAction={onAction} disabledOp={disabledOp} attributes={attributes} data={!disableSerialNumber
? data?.map((ele, index) => {

View File

@ -9,7 +9,7 @@ export default OakComponent({
multiple: false,
entityId: '',
entityIds: [],
pickerRender: {},
pickerRender: {}, // OakAbsRefAttrPickerRender
onChange: (() => undefined),
},
formData() {

View File

@ -5,7 +5,7 @@ export default OakComponent({
return this.props.entity;
},
properties: {
helps: {},
helps: {}, // Record<string, string>;
entity: '',
attributes: [],
layout: 'horizontal',

View File

@ -15,7 +15,7 @@ export declare abstract class BackendRuntimeContext<ED extends EntityDict & Base
getSubscriberId(): string | undefined;
getBriefEnvironment(): BriefEnv | undefined;
protected getSerializedData(): Promise<SerializedData>;
initialize(data?: SerializedData): Promise<void>;
initialize(data?: SerializedData, later?: boolean): Promise<void>;
/**
* event中带id的占位符saveOpRecord时再动态注入 by Xc
* @param operationId

View File

@ -22,7 +22,7 @@ export class BackendRuntimeContext extends AsyncContext {
ns: this.ns,
};
}
async initialize(data) {
async initialize(data, later) {
if (data?.sid) {
this.subscriberId = data.sid;
}

View File

@ -321,7 +321,7 @@ export class Cache extends Feature {
opers.forEach((oper) => {
const { entity, operation } = oper;
this.cacheStore.operate(entity, operation, this.context, {
checkerTypes: ['logical'],
checkerTypes: ['logical'], // 这里不能检查data不然在数据没填完前会有大量异常
dontCollect: true,
});
});

View File

@ -9,7 +9,7 @@ import { Environment } from './environment';
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { Scope, TranslateOptions } from 'i18n-js';
export declare class Locales<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends CommonAspectDict<ED, Cxt>> extends Feature {
static MINIMAL_LOADING_GAP: number;
static REFRESH_STALE_INTERVAL: number;
private cache;
private localStorage;
private environment;

View File

@ -3,7 +3,7 @@ import { assert } from 'oak-domain/lib/utils/assert';
import { I18n } from 'i18n-js';
import { LOCAL_STORAGE_KEYS } from '../constant/constant';
export class Locales extends Feature {
static MINIMAL_LOADING_GAP = 600 * 10000; // 最小加载间歇
static REFRESH_STALE_INTERVAL = 30 * 24 * 3600 * 1000; // 正式环境下的主动刷新缓存策略
cache;
localStorage;
environment;
@ -61,6 +61,7 @@ export class Locales extends Feature {
data: 1,
namespace: 1,
language: 1,
$$updateAt$$: 1,
},
});
const dataset = {};
@ -76,10 +77,23 @@ export class Locales extends Feature {
});
this.i18n.store(dataset);
if (i18ns.length > 0) {
// 启动时刷新数据策略
// 先不处理了这样似乎会导致如果key不miss就不会更新所以i18n的更新要确保这点
/* const nss = i18ns.map(ele => ele.namespace!);
await this.loadServerData(nss); */
/**
* 前台启动时的数据刷新策略
* dev环境无条件刷新production环境只会主动刷新超过REFRESH_STALE_INTERVAL(30)的i18n数据
* 这样会导致在此期间内如果不发生key miss数据不会更新
* 程序员要谨慎对待这一特性对于重要的i18n在版本间尽量不要更新原有的key值
*/
if (process.env.NODE_ENV === 'development') {
const nss = i18ns.map(ele => ele.namespace);
await this.loadServerData(nss);
}
else {
const now = Date.now();
const nss = i18ns.filter(ele => now - ele.$$updateAt$$ > Locales.REFRESH_STALE_INTERVAL).map(ele => ele.namespace);
if (nss.length > 0) {
await this.loadServerData(nss);
}
}
}
}
async loadServerData(nss) {

View File

@ -168,6 +168,7 @@ declare class SingleNode<ED extends EntityDict & BaseEntityDict, T extends keyof
addChild(path: string, node: SingleNode<ED, keyof ED, Cxt, FrontCxt, AD> | ListNode<ED, keyof ED, Cxt, FrontCxt, AD>): void;
removeChild(path: string): void;
getFreshValue(inModi?: boolean): Partial<ED[T]['Schema']> | undefined;
private refreshListChildren;
create(data: Partial<Omit<ED[T]['CreateSingle']['data'], 'id'>>): void;
update(data: ED[T]['Update']['data'], action?: ED[T]['Action']): void;
remove(): void;

View File

@ -787,6 +787,7 @@ class ListNode extends Node {
}
else {
// 不刷新也publish一下触发页面reRender不然有可能导致页面不进入formData
this.sr = {};
this.publish();
}
}
@ -913,12 +914,14 @@ class SingleNode extends Node {
}
assert(!this.dirty, 'setId时结点是dirty在setId之前应当处理掉原有的update');
this.id = id;
this.refreshListChildren();
this.publish();
}
}
unsetId() {
if (this.id) {
this.id = undefined;
this.refreshListChildren();
this.publish();
}
}
@ -963,6 +966,15 @@ class SingleNode extends Node {
return result[0];
}
}
// 当node的id重置时其一对多的儿子结点都应当刷新数据条件已经改变
refreshListChildren() {
for (const k in this.children) {
const child = this.children[k];
if (child instanceof ListNode) {
child.refresh();
}
}
}
create(data) {
const id = generateNewId();
assert(!this.id && !this.dirty, 'create前要保证singleNode为空');
@ -979,6 +991,7 @@ class SingleNode extends Node {
action: 'create',
data: Object.assign({}, data, { id }),
};
this.refreshListChildren();
this.setDirty();
}
update(data, action) {
@ -1294,7 +1307,7 @@ class SingleNode extends Node {
*/
getParentFilter(childNode, ignoreNewParent) {
const value = this.getFreshValue();
if (value && value.$$createAt$$ === 1 && ignoreNewParent) {
if (!value || (value && value.$$createAt$$ === 1 && ignoreNewParent)) {
return;
}
for (const key in this.children) {

View File

@ -1,7 +1,7 @@
import React from 'react';
export const keys = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
export const values = {
xs: 576,
xs: 576, //小于576
sm: 576,
md: 768,
lg: 992,

View File

@ -15,7 +15,7 @@ export declare abstract class BackendRuntimeContext<ED extends EntityDict & Base
getSubscriberId(): string | undefined;
getBriefEnvironment(): BriefEnv | undefined;
protected getSerializedData(): Promise<SerializedData>;
initialize(data?: SerializedData): Promise<void>;
initialize(data?: SerializedData, later?: boolean): Promise<void>;
/**
* event中带id的占位符saveOpRecord时再动态注入 by Xc
* @param operationId

View File

@ -26,7 +26,7 @@ class BackendRuntimeContext extends AsyncRowStore_1.AsyncContext {
ns: this.ns,
};
}
async initialize(data) {
async initialize(data, later) {
if (data?.sid) {
this.subscriberId = data.sid;
}

View File

@ -324,7 +324,7 @@ class Cache extends Feature_1.Feature {
opers.forEach((oper) => {
const { entity, operation } = oper;
this.cacheStore.operate(entity, operation, this.context, {
checkerTypes: ['logical'],
checkerTypes: ['logical'], // 这里不能检查data不然在数据没填完前会有大量异常
dontCollect: true,
});
});

View File

@ -9,7 +9,7 @@ import { Environment } from './environment';
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
import { Scope, TranslateOptions } from 'i18n-js';
export declare class Locales<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends CommonAspectDict<ED, Cxt>> extends Feature {
static MINIMAL_LOADING_GAP: number;
static REFRESH_STALE_INTERVAL: number;
private cache;
private localStorage;
private environment;

View File

@ -6,7 +6,7 @@ const assert_1 = require("oak-domain/lib/utils/assert");
const i18n_js_1 = require("i18n-js");
const constant_1 = require("../constant/constant");
class Locales extends Feature_1.Feature {
static MINIMAL_LOADING_GAP = 600 * 10000; // 最小加载间歇
static REFRESH_STALE_INTERVAL = 30 * 24 * 3600 * 1000; // 正式环境下的主动刷新缓存策略
cache;
localStorage;
environment;
@ -64,6 +64,7 @@ class Locales extends Feature_1.Feature {
data: 1,
namespace: 1,
language: 1,
$$updateAt$$: 1,
},
});
const dataset = {};
@ -79,10 +80,23 @@ class Locales extends Feature_1.Feature {
});
this.i18n.store(dataset);
if (i18ns.length > 0) {
// 启动时刷新数据策略
// 先不处理了这样似乎会导致如果key不miss就不会更新所以i18n的更新要确保这点
/* const nss = i18ns.map(ele => ele.namespace!);
await this.loadServerData(nss); */
/**
* 前台启动时的数据刷新策略
* dev环境无条件刷新production环境只会主动刷新超过REFRESH_STALE_INTERVAL(30)的i18n数据
* 这样会导致在此期间内如果不发生key miss数据不会更新
* 程序员要谨慎对待这一特性对于重要的i18n在版本间尽量不要更新原有的key值
*/
if (process.env.NODE_ENV === 'development') {
const nss = i18ns.map(ele => ele.namespace);
await this.loadServerData(nss);
}
else {
const now = Date.now();
const nss = i18ns.filter(ele => now - ele.$$updateAt$$ > Locales.REFRESH_STALE_INTERVAL).map(ele => ele.namespace);
if (nss.length > 0) {
await this.loadServerData(nss);
}
}
}
}
async loadServerData(nss) {

View File

@ -168,6 +168,7 @@ declare class SingleNode<ED extends EntityDict & BaseEntityDict, T extends keyof
addChild(path: string, node: SingleNode<ED, keyof ED, Cxt, FrontCxt, AD> | ListNode<ED, keyof ED, Cxt, FrontCxt, AD>): void;
removeChild(path: string): void;
getFreshValue(inModi?: boolean): Partial<ED[T]['Schema']> | undefined;
private refreshListChildren;
create(data: Partial<Omit<ED[T]['CreateSingle']['data'], 'id'>>): void;
update(data: ED[T]['Update']['data'], action?: ED[T]['Action']): void;
remove(): void;

View File

@ -790,6 +790,7 @@ class ListNode extends Node {
}
else {
// 不刷新也publish一下触发页面reRender不然有可能导致页面不进入formData
this.sr = {};
this.publish();
}
}
@ -916,12 +917,14 @@ class SingleNode extends Node {
}
(0, assert_1.assert)(!this.dirty, 'setId时结点是dirty在setId之前应当处理掉原有的update');
this.id = id;
this.refreshListChildren();
this.publish();
}
}
unsetId() {
if (this.id) {
this.id = undefined;
this.refreshListChildren();
this.publish();
}
}
@ -966,6 +969,15 @@ class SingleNode extends Node {
return result[0];
}
}
// 当node的id重置时其一对多的儿子结点都应当刷新数据条件已经改变
refreshListChildren() {
for (const k in this.children) {
const child = this.children[k];
if (child instanceof ListNode) {
child.refresh();
}
}
}
create(data) {
const id = (0, uuid_1.generateNewId)();
(0, assert_1.assert)(!this.id && !this.dirty, 'create前要保证singleNode为空');
@ -982,6 +994,7 @@ class SingleNode extends Node {
action: 'create',
data: Object.assign({}, data, { id }),
};
this.refreshListChildren();
this.setDirty();
}
update(data, action) {
@ -1297,7 +1310,7 @@ class SingleNode extends Node {
*/
getParentFilter(childNode, ignoreNewParent) {
const value = this.getFreshValue();
if (value && value.$$createAt$$ === 1 && ignoreNewParent) {
if (!value || (value && value.$$createAt$$ === 1 && ignoreNewParent)) {
return;
}
for (const key in this.children) {

View File

@ -5,7 +5,7 @@ const tslib_1 = require("tslib");
const react_1 = tslib_1.__importDefault(require("react"));
exports.keys = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
exports.values = {
xs: 576,
xs: 576, //小于576
sm: 576,
md: 768,
lg: 992,

View File

@ -1,6 +1,6 @@
{
"name": "oak-frontend-base",
"version": "4.2.6",
"version": "4.2.7",
"description": "oak框架中前端与业务逻辑无关的平台部分",
"author": {
"name": "XuChang"
@ -22,8 +22,8 @@
"i18n-js": "^4.3.0",
"node-schedule": "^2.1.1",
"oak-common-aspect": "^2.2.5",
"oak-domain": "^4.2.4",
"oak-memory-tree-store": "^3.1.4",
"oak-domain": "^4.2.8",
"oak-memory-tree-store": "^3.1.5",
"ol": "^7.3.0",
"react-native-device-info": "^10.12.0",
"react-native-localize": "^3.0.4",

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Space, Tooltip, ButtonProps } from 'antd';
import {
ReloadOutlined
ReloadOutlined
} from '@ant-design/icons';
import ButtonGroup from '../buttonGroup';
import ColumnSetting from '../columnSetting';
@ -13,13 +13,13 @@ type buttonProps = {
label: string;
type?: ButtonProps['type'];
onClick: () => void;
}
}
type ToolBarProps = {
title?: React.ReactNode;
buttonGroup?: buttonProps[];
extraContent?: React.ReactNode;
reload: () => void;
reload?: () => void;
}
function ToolBar(props: ToolBarProps) {
@ -35,16 +35,17 @@ function ToolBar(props: ToolBarProps) {
{buttonGroup && buttonGroup.length > 0 && (
<ButtonGroup items={buttonGroup} />
)}
<Tooltip title={features.locales.t('reload')}>
<div
className={Style.reloadIconBox}
onClick={() => {
reload();
}}
>
<ReloadOutlined />
</div>
</Tooltip>
{reload &&
<Tooltip title={features.locales.t('reload')}>
<div
className={Style.reloadIconBox}
onClick={() => {
reload!();
}}
>
<ReloadOutlined />
</div>
</Tooltip>}
<ColumnSetting />
</Space>
</div>

View File

@ -162,9 +162,7 @@ const ProList = <ED2 extends ED, T extends keyof ED2>(props: Props<ED2, T>) => {
title={title}
extraContent={extraContent}
buttonGroup={buttonGroup}
reload={() => {
onReload && onReload();
}}
reload={onReload}
/>
)}
{isMobile && <ButtonGroup items={buttonGroup} />}

View File

@ -35,7 +35,7 @@ export abstract class BackendRuntimeContext<ED extends EntityDict & BaseEntityDi
}
}
async initialize(data?: SerializedData) {
async initialize(data?: SerializedData, later?: boolean) {
if (data?.sid) {
this.subscriberId = data.sid;
}

View File

@ -13,7 +13,7 @@ import { I18n, Scope, TranslateOptions } from 'i18n-js';
import { LOCAL_STORAGE_KEYS } from '../constant/constant';
export class Locales<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends CommonAspectDict<ED, Cxt>> extends Feature {
static MINIMAL_LOADING_GAP = 600 * 10000; // 最小加载间歇
static REFRESH_STALE_INTERVAL = 30 * 24 * 3600 * 1000; // 正式环境下的主动刷新缓存策略
private cache: Cache<ED, Cxt, FrontCxt, AD>;
private localStorage: LocalStorage;
private environment: Environment;
@ -83,6 +83,7 @@ export class Locales<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncCo
data: 1,
namespace: 1,
language: 1,
$$updateAt$$: 1,
},
});
const dataset: Record<string, any> = {};
@ -101,10 +102,25 @@ export class Locales<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncCo
this.i18n.store(dataset);
if (i18ns.length > 0) {
// 启动时刷新数据策略
// 先不处理了这样似乎会导致如果key不miss就不会更新所以i18n的更新要确保这点
/* const nss = i18ns.map(ele => ele.namespace!);
await this.loadServerData(nss); */
/**
*
* dev环境无条件刷新production环境只会主动刷新超过REFRESH_STALE_INTERVAL(30)i18n数据
* key miss
* i18nkey值
*/
if (process.env.NODE_ENV === 'development') {
const nss = i18ns.map(ele => ele.namespace!);
await this.loadServerData(nss);
}
else {
const now = Date.now();
const nss = i18ns.filter(
ele => now - (ele.$$updateAt$$ as number) > Locales.REFRESH_STALE_INTERVAL
).map(ele => ele.namespace!);
if (nss.length > 0) {
await this.loadServerData(nss);
}
}
}
}

View File

@ -971,6 +971,7 @@ class ListNode<
}
else {
// 不刷新也publish一下触发页面reRender不然有可能导致页面不进入formData
this.sr = {};
this.publish();
}
}
@ -1130,6 +1131,7 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
}
assert(!this.dirty, 'setId时结点是dirty在setId之前应当处理掉原有的update');
this.id = id;
this.refreshListChildren();
this.publish();
}
}
@ -1137,6 +1139,7 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
unsetId() {
if (this.id) {
this.id = undefined;
this.refreshListChildren();
this.publish();
}
}
@ -1187,6 +1190,16 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
}
}
// 当node的id重置时其一对多的儿子结点都应当刷新数据条件已经改变
private refreshListChildren() {
for (const k in this.children) {
const child = this.children[k];
if (child instanceof ListNode) {
child.refresh();
}
}
}
create(data: Partial<Omit<ED[T]['CreateSingle']['data'], 'id'>>) {
const id = generateNewId();
assert(!this.id && !this.dirty, 'create前要保证singleNode为空');
@ -1203,6 +1216,7 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
action: 'create',
data: Object.assign({}, data, { id }),
};
this.refreshListChildren();
this.setDirty();
}
@ -1544,7 +1558,7 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
getParentFilter<T2 extends keyof ED>(childNode: ListNode<ED, keyof ED, Cxt, FrontCxt, AD>, ignoreNewParent?: boolean): ED[T2]['Selection']['filter'] | undefined {
const value = this.getFreshValue();
if (value && value.$$createAt$$ === 1 && ignoreNewParent) {
if (!value || (value && value.$$createAt$$ === 1 && ignoreNewParent)) {
return;
}
for (const key in this.children) {