Compare commits
33 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
4929590c81 | |
|
|
567e0abc6a | |
|
|
a26f464ba1 | |
|
|
fdd24a8cc8 | |
|
|
fff9750b2a | |
|
|
f16b3f91a8 | |
|
|
6e48eb02b4 | |
|
|
b07f084341 | |
|
|
96b8cd9366 | |
|
|
363b329070 | |
|
|
51cc8786a0 | |
|
|
ea01036c5a | |
|
|
71f385f0f4 | |
|
|
91bf1da808 | |
|
|
81b12efe6e | |
|
|
40a98dd51a | |
|
|
94dc93868c | |
|
|
a9a358727b | |
|
|
b8e3dcd24a | |
|
|
3ca9d51d4d | |
|
|
73de8a74c0 | |
|
|
5c710ef019 | |
|
|
2e214df7fd | |
|
|
626b40b4e3 | |
|
|
39ee45fee2 | |
|
|
306c0a75ef | |
|
|
5a3d06d037 | |
|
|
e4d8ed5e03 | |
|
|
9541e67508 | |
|
|
285c872822 | |
|
|
f52d28a4e5 | |
|
|
9d71fd9c30 | |
|
|
90a2125ddb |
|
|
@ -19,7 +19,7 @@ MiddleSelect.Option = Select.Option;
|
||||||
export default function Render(props) {
|
export default function Render(props) {
|
||||||
const { style, className, oakPagination, oakFullpath, newTotal, showQuickJumper, showSizeChanger, size, showTotal, showTitle, } = props.data;
|
const { style, className, oakPagination, oakFullpath, newTotal, showQuickJumper, showSizeChanger, size, showTotal, showTitle, } = props.data;
|
||||||
const { t, setPageSize, setCurrentPage } = props.methods;
|
const { t, setPageSize, setCurrentPage } = props.methods;
|
||||||
const { pageSize, total, currentPage, more, count, getTotal } = oakPagination || {};
|
const { pageSize = 0, total = 0, currentPage = 1, more, count, getTotal } = oakPagination || {};
|
||||||
const paginationRef = useRef(null);
|
const paginationRef = useRef(null);
|
||||||
const [internalInputVal, setInternalInputVal] = useState(currentPage);
|
const [internalInputVal, setInternalInputVal] = useState(currentPage);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -28,7 +28,7 @@ export default function Render(props) {
|
||||||
if (!oakPagination) {
|
if (!oakPagination) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (total === 0) {
|
if (!total) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const prefixCls = 'rc-pagination';
|
const prefixCls = 'rc-pagination';
|
||||||
|
|
|
||||||
|
|
@ -75,13 +75,12 @@ export class Navigator extends CommonNavigator {
|
||||||
wx.navigateTo({
|
wx.navigateTo({
|
||||||
url: url,
|
url: url,
|
||||||
success: () => {
|
success: () => {
|
||||||
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.publish();
|
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 关闭当前页面,跳转到应用内的某个页面,但不允许跳转到tabBar页面。
|
// 关闭当前页面,跳转到应用内的某个页面,但不允许跳转到tabBar页面。
|
||||||
|
|
@ -94,12 +93,12 @@ export class Navigator extends CommonNavigator {
|
||||||
wx.redirectTo({
|
wx.redirectTo({
|
||||||
url: url,
|
url: url,
|
||||||
success: () => {
|
success: () => {
|
||||||
resolve(undefined);
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//跳转到tabBar页面,并关闭其他所有非tabBar页面,用于跳转到主页。
|
//跳转到tabBar页面,并关闭其他所有非tabBar页面,用于跳转到主页。
|
||||||
|
|
@ -112,12 +111,12 @@ export class Navigator extends CommonNavigator {
|
||||||
wx.switchTab({
|
wx.switchTab({
|
||||||
url: url,
|
url: url,
|
||||||
success: () => {
|
success: () => {
|
||||||
resolve(undefined);
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
navigateBack(delta) {
|
navigateBack(delta) {
|
||||||
|
|
@ -128,29 +127,23 @@ export class Navigator extends CommonNavigator {
|
||||||
wx.navigateBack({
|
wx.navigateBack({
|
||||||
delta: delta || 1,
|
delta: delta || 1,
|
||||||
success: () => {
|
success: () => {
|
||||||
resolve(undefined);
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
||||||
if (!this.enter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pages = getCurrentPages();
|
const pages = getCurrentPages();
|
||||||
if (pages.length > 1) {
|
if (pages.length > 1) {
|
||||||
this.leave();
|
|
||||||
return this.navigateBack();
|
return this.navigateBack();
|
||||||
}
|
}
|
||||||
const isTabBar = options?.isTabBar;
|
const isTabBar = options?.isTabBar;
|
||||||
if (isTabBar) {
|
if (isTabBar) {
|
||||||
this.leave();
|
|
||||||
return this.switchTab(options, state, disableNamespace);
|
return this.switchTab(options, state, disableNamespace);
|
||||||
}
|
}
|
||||||
this.leave();
|
|
||||||
return this.redirectTo(options, state, disableNamespace);
|
return this.redirectTo(options, state, disableNamespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ export class Navigator extends CommonNavigator {
|
||||||
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
||||||
const replaceAction = StackActions.replace(url, props);
|
const replaceAction = StackActions.replace(url, props);
|
||||||
this.history.dispatch(replaceAction);
|
this.history.dispatch(replaceAction);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
async switchTab(options, state, disableNamespace) {
|
async switchTab(options, state, disableNamespace) {
|
||||||
if (!this.enter()) {
|
if (!this.enter()) {
|
||||||
|
|
@ -66,8 +66,8 @@ export class Navigator extends CommonNavigator {
|
||||||
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
||||||
const jumpToAction = TabActions.jumpTo(url, props);
|
const jumpToAction = TabActions.jumpTo(url, props);
|
||||||
this.history.dispatch(jumpToAction);
|
this.history.dispatch(jumpToAction);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
async navigateBack(delta) {
|
async navigateBack(delta) {
|
||||||
if (!this.enter()) {
|
if (!this.enter()) {
|
||||||
|
|
@ -77,9 +77,12 @@ export class Navigator extends CommonNavigator {
|
||||||
if (canGoBack) {
|
if (canGoBack) {
|
||||||
const popAction = StackActions.pop(delta || 1);
|
const popAction = StackActions.pop(delta || 1);
|
||||||
this.history.dispatch(popAction);
|
this.history.dispatch(popAction);
|
||||||
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
}
|
}
|
||||||
this.leave();
|
else {
|
||||||
|
this.leave();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
||||||
if (!this.enter()) {
|
if (!this.enter()) {
|
||||||
|
|
@ -88,13 +91,13 @@ export class Navigator extends CommonNavigator {
|
||||||
const canGoBack = this.history.canGoBack();
|
const canGoBack = this.history.canGoBack();
|
||||||
if (canGoBack) {
|
if (canGoBack) {
|
||||||
this.navigateBack();
|
this.navigateBack();
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 回最顶层
|
// 回最顶层
|
||||||
this.history.dispatch(StackActions.popToTop());
|
this.history.dispatch(StackActions.popToTop());
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ export class Navigator extends CommonNavigator {
|
||||||
}
|
}
|
||||||
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
||||||
this.history.push(url, props);
|
this.history.push(url, props);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
async redirectTo(options, state, disableNamespace) {
|
async redirectTo(options, state, disableNamespace) {
|
||||||
if (!this.enter()) {
|
if (!this.enter()) {
|
||||||
|
|
@ -54,8 +54,8 @@ export class Navigator extends CommonNavigator {
|
||||||
}
|
}
|
||||||
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
||||||
this.history.replace(url, props);
|
this.history.replace(url, props);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
async switchTab(options, state, disableNamespace) {
|
async switchTab(options, state, disableNamespace) {
|
||||||
console.error('浏览器无switchTab');
|
console.error('浏览器无switchTab');
|
||||||
|
|
@ -68,8 +68,8 @@ export class Navigator extends CommonNavigator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.history.go(delta ? 0 - delta : -1);
|
this.history.go(delta ? 0 - delta : -1);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
||||||
console.error('浏览器暂无法获得history堆栈');
|
console.error('浏览器暂无法获得history堆栈');
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ declare class SingleNode<ED extends EntityDict & BaseEntityDict, T extends keyof
|
||||||
saveRefreshResult(data: Record<string, any>): void;
|
saveRefreshResult(data: Record<string, any>): void;
|
||||||
refresh(): Promise<void>;
|
refresh(): Promise<void>;
|
||||||
clean(lsn?: number, dontPublish?: true): void;
|
clean(lsn?: number, dontPublish?: true): void;
|
||||||
private getFilter;
|
getFilter(ignoreNew?: true, onlyHot?: true): ED[T]['Filter'] | undefined;
|
||||||
getIntrinsticFilters(): ED[T]["Filter"] | undefined;
|
getIntrinsticFilters(): ED[T]["Filter"] | undefined;
|
||||||
/**
|
/**
|
||||||
* getParentFilter不能假设一定已经有数据,只能根据当前filter的条件去构造
|
* getParentFilter不能假设一定已经有数据,只能根据当前filter的条件去构造
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { createOperationsFromModies } from 'oak-domain/lib/store/modi';
|
||||||
import { judgeRelation } from "oak-domain/lib/store/relation";
|
import { judgeRelation } from "oak-domain/lib/store/relation";
|
||||||
import { CreateAtAttribute, UpdateAtAttribute, DeleteAtAttribute } from "oak-domain/lib/types";
|
import { CreateAtAttribute, UpdateAtAttribute, DeleteAtAttribute } from "oak-domain/lib/types";
|
||||||
import { Feature } from '../types/Feature';
|
import { Feature } from '../types/Feature';
|
||||||
import { generateNewId } from 'oak-domain/lib/utils/uuid';
|
import { generateNewId, generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||||
export const MODI_NEXT_PATH_SUFFIX = ':next';
|
export const MODI_NEXT_PATH_SUFFIX = ':next';
|
||||||
const START_LSN = 100;
|
const START_LSN = 100;
|
||||||
function mergeOperation(schema, entity, oper1, oper2) {
|
function mergeOperation(schema, entity, oper1, oper2) {
|
||||||
|
|
@ -537,14 +537,14 @@ class ListNode extends EntityNode {
|
||||||
/** 检查原本就在的,现在还在不在,
|
/** 检查原本就在的,现在还在不在,
|
||||||
* 以及原本不在的,现在是不是满足条件了
|
* 以及原本不在的,现在是不是满足条件了
|
||||||
* (后一种情况也可能原本就满足但不在当前的ids中,这时是判断不出来的,total+1可能不对,但是应该是较少的case)*/
|
* (后一种情况也可能原本就满足但不在当前的ids中,这时是判断不出来的,total+1可能不对,但是应该是较少的case)*/
|
||||||
const filter = this.constructFilters(true, true, true);
|
const filters = this.constructFilters(true, true, true);
|
||||||
if (intersected.length) {
|
if (intersected.length) {
|
||||||
const rows = this.cache.get(this.entity, {
|
const rows = this.cache.get(this.entity, {
|
||||||
data: {
|
data: {
|
||||||
id: 1,
|
id: 1,
|
||||||
},
|
},
|
||||||
filter: combineFilters(this.entity, this.schema, [
|
filter: combineFilters(this.entity, this.schema, [
|
||||||
...(filter || []),
|
...(filters || []),
|
||||||
{
|
{
|
||||||
id: {
|
id: {
|
||||||
$in: intersected,
|
$in: intersected,
|
||||||
|
|
@ -562,12 +562,8 @@ class ListNode extends EntityNode {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (diffed.length) {
|
if (diffed.length) {
|
||||||
diffed.forEach((ele) => {
|
const filter = filters && combineFilters(e, this.schema, filters);
|
||||||
this.sr[ele] = {};
|
diffed.forEach((ele) => tryAddRowToList(ele, filter));
|
||||||
if (this.pagination.total) {
|
|
||||||
this.pagination.total++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// hasUpdated = true;
|
// hasUpdated = true;
|
||||||
|
|
@ -1256,7 +1252,7 @@ class SingleNode extends EntityNode {
|
||||||
setId(id) {
|
setId(id) {
|
||||||
if (id !== this.id) {
|
if (id !== this.id) {
|
||||||
const operations = this.ulManager.makeOperations();
|
const operations = this.ulManager.makeOperations();
|
||||||
assert(operations.length <= 1);
|
assert(operations.length <= 1, 'singleNode在setId时出现数据不一致');
|
||||||
const [operation] = operations;
|
const [operation] = operations;
|
||||||
if (operation?.action === 'create') {
|
if (operation?.action === 'create') {
|
||||||
if (operation.data.id === id) {
|
if (operation.data.id === id) {
|
||||||
|
|
@ -1417,7 +1413,7 @@ class SingleNode extends EntityNode {
|
||||||
}
|
}
|
||||||
setDirty() {
|
setDirty() {
|
||||||
const id = this.getId();
|
const id = this.getId();
|
||||||
assert(id);
|
assert(id, "不能对没有id的singleNode设置dirty");
|
||||||
this.ulManager.push(this.ulManager.maxLsn, {
|
this.ulManager.push(this.ulManager.maxLsn, {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
data: {},
|
data: {},
|
||||||
|
|
@ -1445,7 +1441,7 @@ class SingleNode extends EntityNode {
|
||||||
const childOperations = child.composeOperations(paths?.length ? paths : undefined);
|
const childOperations = child.composeOperations(paths?.length ? paths : undefined);
|
||||||
if (childOperations) {
|
if (childOperations) {
|
||||||
if (child instanceof SingleNode) {
|
if (child instanceof SingleNode) {
|
||||||
assert(childOperations.length === 1);
|
assert(childOperations.length === 1, 'singleNode在composeOperations时出现数据不一致');
|
||||||
this.ulManager.push(lsnMax + 100, {
|
this.ulManager.push(lsnMax + 100, {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -1457,7 +1453,7 @@ class SingleNode extends EntityNode {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assert(child instanceof ListNode);
|
assert(child instanceof ListNode, 'child必须是ListNode');
|
||||||
this.ulManager.push(lsnMax + 100, {
|
this.ulManager.push(lsnMax + 100, {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -1547,8 +1543,8 @@ class SingleNode extends EntityNode {
|
||||||
const rel = this.judgeRelation(k2);
|
const rel = this.judgeRelation(k2);
|
||||||
if (rel === 2) {
|
if (rel === 2) {
|
||||||
if (value?.entityId) {
|
if (value?.entityId) {
|
||||||
assert(child instanceof SingleNode);
|
assert(child instanceof SingleNode, 'child必须是singleNode');
|
||||||
assert(value.entity === child.getEntity());
|
assert(value.entity === child.getEntity(), 'singleNode的entity必须一致');
|
||||||
child.saveRefreshResult({
|
child.saveRefreshResult({
|
||||||
[value.entityId]: this.sr[k2] || {},
|
[value.entityId]: this.sr[k2] || {},
|
||||||
});
|
});
|
||||||
|
|
@ -2011,6 +2007,7 @@ export class RunningTree extends Feature {
|
||||||
assert(!parentNode || parentNode instanceof VirtualNode);
|
assert(!parentNode || parentNode instanceof VirtualNode);
|
||||||
node = new VirtualNode(fullPath, path, parentNode, stale);
|
node = new VirtualNode(fullPath, path, parentNode, stale);
|
||||||
}
|
}
|
||||||
|
// 任何临时的修改都需要回滚,上面缓存了一些update操作
|
||||||
rollback();
|
rollback();
|
||||||
if (!parentNode) {
|
if (!parentNode) {
|
||||||
assert(!parent && !this.root[path]);
|
assert(!parent && !this.root[path]);
|
||||||
|
|
@ -2417,7 +2414,7 @@ export class RunningTree extends Feature {
|
||||||
const node = path && this.findNode(path);
|
const node = path && this.findNode(path);
|
||||||
// assert(node.isDirty());
|
// assert(node.isDirty());
|
||||||
node && node.setExecuting(true);
|
node && node.setExecuting(true);
|
||||||
let pollute = false;
|
// let pollute = false;
|
||||||
try {
|
try {
|
||||||
let operations = path && this.getOperations(path) || [];
|
let operations = path && this.getOperations(path) || [];
|
||||||
if (opers) {
|
if (opers) {
|
||||||
|
|
@ -2436,15 +2433,30 @@ export class RunningTree extends Feature {
|
||||||
else if (node) {
|
else if (node) {
|
||||||
// 老的写法,直接对一个非脏的结点execute某个action,也可以支持
|
// 老的写法,直接对一个非脏的结点execute某个action,也可以支持
|
||||||
assert(node instanceof SingleNode);
|
assert(node instanceof SingleNode);
|
||||||
node.update(this.logSerailNumber, {}, action);
|
// node.update(this.logSerailNumber, {}, action);
|
||||||
pollute = true;
|
// pollute = true;
|
||||||
operations = node.composeOperations() || [];
|
// operations = node.composeOperations() || [];
|
||||||
assert(operations.length === 1);
|
// assert(operations.length === 1);
|
||||||
const [operation1] = operations;
|
// const [operation1] = operations;
|
||||||
if (action !== operation1.operation.action) {
|
// if (action !== operation1.operation.action) {
|
||||||
assert(operation1.operation.action === 'update'); // 如果execute时传action,前面update动作应该只可能是update
|
// assert(operation1.operation.action === 'update'); // 如果execute时传action,前面update动作应该只可能是update
|
||||||
operation1.operation.action = action;
|
// operation1.operation.action = action;
|
||||||
}
|
// }
|
||||||
|
/**
|
||||||
|
* 上述写法会触发publish行为,如:一个close动作会导致页面像下面这样渲染:
|
||||||
|
* opened --> closed(上面的node.update) --> opened(执行成功回来setExecuting时) --> closed(执行成功回来再sync cache后)
|
||||||
|
*/
|
||||||
|
operations.push({
|
||||||
|
entity: node.getEntity(),
|
||||||
|
operation: {
|
||||||
|
id: await generateNewIdAsync(),
|
||||||
|
action,
|
||||||
|
data: {
|
||||||
|
$$updateAt$$: Date.now(),
|
||||||
|
},
|
||||||
|
filter: node.getFilter(true),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (operations.length > 0) {
|
if (operations.length > 0) {
|
||||||
|
|
@ -2474,9 +2486,9 @@ export class RunningTree extends Feature {
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
node && node.setExecuting(false);
|
node && node.setExecuting(false);
|
||||||
if (pollute) {
|
/* if (pollute) {
|
||||||
path && this.clean(path);
|
path && this.clean(path);
|
||||||
}
|
} */
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,11 +94,11 @@ export class SubScriber extends Feature {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
socket.on('data', (opRecords, event) => {
|
socket.on('data', (opRecords, event) => {
|
||||||
|
this.cache.sync(opRecords);
|
||||||
const registered = this.eventMap[event];
|
const registered = this.eventMap[event];
|
||||||
if (registered) {
|
if (registered) {
|
||||||
registered.callbacks.forEach((ele) => ele(event, opRecords));
|
registered.callbacks.forEach((ele) => ele(event, opRecords));
|
||||||
}
|
}
|
||||||
this.cache.sync(opRecords);
|
|
||||||
});
|
});
|
||||||
socket.on('error', (errString) => {
|
socket.on('error', (errString) => {
|
||||||
console.error(errString);
|
console.error(errString);
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@ import { Feature } from './types/Feature';
|
||||||
import { DataOption, OakComponentOption } from './types/Page';
|
import { DataOption, OakComponentOption } from './types/Page';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends DataOption = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): string;
|
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends DataOption = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): string;
|
||||||
|
|
|
||||||
|
|
@ -540,6 +540,13 @@ function translatePropertiesToPropertyDefinitions(properties) {
|
||||||
const definitions = {};
|
const definitions = {};
|
||||||
if (properties) {
|
if (properties) {
|
||||||
Object.keys(properties).forEach((prop) => {
|
Object.keys(properties).forEach((prop) => {
|
||||||
|
if (properties[prop] === null) {
|
||||||
|
definitions[prop] = {
|
||||||
|
type: null,
|
||||||
|
value: null,
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (typeof properties[prop]) {
|
switch (typeof properties[prop]) {
|
||||||
case 'string': {
|
case 'string': {
|
||||||
if (properties[prop]) {
|
if (properties[prop]) {
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ import { Feature } from './types/Feature';
|
||||||
import { DataOption, OakComponentOption } from './types/Page';
|
import { DataOption, OakComponentOption } from './types/Page';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
|
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { MessageProps } from './types/Message';
|
||||||
import { NotificationProps } from './types/Notification';
|
import { NotificationProps } from './types/Notification';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): {
|
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): {
|
||||||
new (props: ComponentProps<ED, T, TProperty>): {
|
new (props: ComponentProps<ED, T, TProperty>): {
|
||||||
features: BasicFeatures<ED> & FD;
|
features: BasicFeatures<ED> & FD;
|
||||||
oakOption: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>;
|
oakOption: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>;
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ import { Feature } from './types/Feature';
|
||||||
import { DataOption, OakComponentOption } from './types/Page';
|
import { DataOption, OakComponentOption } from './types/Page';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
|
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ export type ComponentFullThisType<ED extends EntityDict & BaseEntityDict, T exte
|
||||||
triggerEvent: <DetailType = any>(name: string, detail?: DetailType, options?: WechatMiniprogram.Component.TriggerEventOption) => void;
|
triggerEvent: <DetailType = any>(name: string, detail?: DetailType, options?: WechatMiniprogram.Component.TriggerEventOption) => void;
|
||||||
oakLifetime: OakLifetime;
|
oakLifetime: OakLifetime;
|
||||||
} & OakCommonComponentMethods<ED, T> & OakListComponentMethods<ED, T> & OakSingleComponentMethods<ED, T>;
|
} & OakCommonComponentMethods<ED, T> & OakListComponentMethods<ED, T> & OakSingleComponentMethods<ED, T>;
|
||||||
export type OakComponentOption<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any>, TProperty extends DataOption, TMethod extends Record<string, Function>, EMethod extends Record<string, Function> = {}> = ComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod, EMethod> & Partial<{
|
export type OakComponentOption<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any>, TProperty extends DataOption, TMethod extends Record<string, Function>, EMethod extends Record<string, Function> = {}> = ComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod, EMethod> & Partial<{
|
||||||
/**
|
/**
|
||||||
* 生命周期回调
|
* 生命周期回调
|
||||||
*/
|
*/
|
||||||
|
|
@ -351,7 +351,7 @@ export type OakComponentData<ED extends EntityDict & BaseEntityDict, T extends k
|
||||||
type OakListComoponetData<ED extends EntityDict & BaseEntityDict, T extends keyof ED> = {
|
type OakListComoponetData<ED extends EntityDict & BaseEntityDict, T extends keyof ED> = {
|
||||||
oakPagination?: Pagination;
|
oakPagination?: Pagination;
|
||||||
};
|
};
|
||||||
export type MakeOakComponent<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>> = <IsList extends boolean, T extends keyof ED, FormedData extends DataOption, TData extends DataOption, TProperty extends DataOption, TMethod extends MethodOption>(options: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>) => (props: ReactComponentProps<ED, T, IsList, TProperty>) => React.ReactElement;
|
export type MakeOakComponent<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>> = <IsList extends boolean, T extends keyof ED, FormedData extends DataOption, TData extends DataOption, TProperty extends DataOption, TMethod extends MethodOption>(options: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>) => (props: ReactComponentProps<ED, T, IsList, TProperty>) => React.ReactElement;
|
||||||
export type WebComponentCommonMethodNames = 'setNotification' | 'setMessage' | 'navigateTo' | 'navigateBack' | 'redirectTo' | 'clean' | 't' | 'execute' | 'refresh' | 'aggregate' | 'checkOperation' | 'isDirty';
|
export type WebComponentCommonMethodNames = 'setNotification' | 'setMessage' | 'navigateTo' | 'navigateBack' | 'redirectTo' | 'clean' | 't' | 'execute' | 'refresh' | 'aggregate' | 'checkOperation' | 'isDirty';
|
||||||
export type WebComponentListMethodNames = 'loadMore' | 'setFilters' | 'addNamedFilter' | 'removeNamedFilter' | 'removeNamedFilterByName' | 'setNamedSorters' | 'addNamedSorter' | 'removeNamedSorter' | 'removeNamedSorterByName' | 'setPageSize' | 'setCurrentPage' | 'addItem' | 'addItems' | 'removeItem' | 'removeItems' | 'updateItem' | 'updateItems' | 'resetItem' | 'recoverItem' | 'recoverItems';
|
export type WebComponentListMethodNames = 'loadMore' | 'setFilters' | 'addNamedFilter' | 'removeNamedFilter' | 'removeNamedFilterByName' | 'setNamedSorters' | 'addNamedSorter' | 'removeNamedSorter' | 'removeNamedSorterByName' | 'setPageSize' | 'setCurrentPage' | 'addItem' | 'addItems' | 'removeItem' | 'removeItems' | 'updateItem' | 'updateItems' | 'resetItem' | 'recoverItem' | 'recoverItems';
|
||||||
export type WebComponentSingleMethodNames = 'update' | 'remove' | 'create' | 'isCreation' | 'getId' | 'setId';
|
export type WebComponentSingleMethodNames = 'update' | 'remove' | 'create' | 'isCreation' | 'getId' | 'setId';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
type UploadFileFn = (options: {
|
||||||
|
file: string | File | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
isFilePath?: boolean;
|
||||||
|
}) => Promise<{
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
statusCode?: number;
|
||||||
|
headers: {
|
||||||
|
get(name: string): string | null;
|
||||||
|
};
|
||||||
|
json(): Promise<any>;
|
||||||
|
text(): Promise<string>;
|
||||||
|
errMsg?: string;
|
||||||
|
data?: any;
|
||||||
|
}>;
|
||||||
|
export interface UploadInterface {
|
||||||
|
uploadFile: UploadFileFn;
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
getActiveUploads(): string[];
|
||||||
|
}
|
||||||
|
export {};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
||||||
|
|
@ -1,3 +1,21 @@
|
||||||
export declare class Upload {
|
import { UploadInterface } from "../types/Upload";
|
||||||
uploadFile(file: string | File, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean): Promise<any>;
|
export declare class Upload implements UploadInterface {
|
||||||
|
uploadFile(options: {
|
||||||
|
file: string | File | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
isFilePath?: boolean;
|
||||||
|
}): Promise<any>;
|
||||||
|
private controllers;
|
||||||
|
constructor();
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
private generateUploadId;
|
||||||
|
getActiveUploads(): string[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,35 @@
|
||||||
export class Upload {
|
export class Upload {
|
||||||
async uploadFile(file, name, uploadUrl, formData, autoInform) {
|
async uploadFile(options) {
|
||||||
console.warn('server不会调用此函数');
|
console.warn('server不会调用此函数');
|
||||||
}
|
}
|
||||||
|
controllers = new Map();
|
||||||
|
constructor() {
|
||||||
|
this.uploadFile = this.uploadFile.bind(this);
|
||||||
|
this.abortUpload = this.abortUpload.bind(this);
|
||||||
|
this.abortAllUploads = this.abortAllUploads.bind(this);
|
||||||
|
this.getUploadStatus = this.getUploadStatus.bind(this);
|
||||||
|
this.getActiveUploads = this.getActiveUploads.bind(this);
|
||||||
|
}
|
||||||
|
// 中断特定上传
|
||||||
|
abortUpload(uploadId) {
|
||||||
|
console.warn('server不会调用此函数');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 中断所有上传
|
||||||
|
abortAllUploads() {
|
||||||
|
console.warn('server不会调用此函数');
|
||||||
|
}
|
||||||
|
// 获取上传状态
|
||||||
|
getUploadStatus(uploadId) {
|
||||||
|
console.warn('server不会调用此函数');
|
||||||
|
return 'uploading';
|
||||||
|
}
|
||||||
|
// 生成唯一的上传ID
|
||||||
|
generateUploadId(file, uploadUrl) {
|
||||||
|
return `server不会调用此函数`;
|
||||||
|
}
|
||||||
|
// 获取所有进行中的上传任务
|
||||||
|
getActiveUploads() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,18 @@
|
||||||
export declare class Upload {
|
import { UploadInterface } from '../types/Upload';
|
||||||
uploadFile(file: string | File, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, method?: "POST" | "PUT" | "PATCH"): Promise<any>;
|
export declare class Upload implements UploadInterface {
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
getActiveUploads(): string[];
|
||||||
|
uploadFile(options: {
|
||||||
|
file: string | File | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
isFilePath?: boolean;
|
||||||
|
}): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,47 @@
|
||||||
import { promisify } from './promisify';
|
import { promisify } from './promisify';
|
||||||
export class Upload {
|
export class Upload {
|
||||||
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, method = "POST") {
|
abortUpload(uploadId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
abortAllUploads() {
|
||||||
|
}
|
||||||
|
getUploadStatus(uploadId) {
|
||||||
|
return 'not-found';
|
||||||
|
}
|
||||||
|
getActiveUploads() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
async uploadFile(options) {
|
||||||
|
const { file, name, uploadUrl, formData, isFilePath, method = "POST" } = options;
|
||||||
const isPut = method === "PUT";
|
const isPut = method === "PUT";
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
return new Promise((resolve, reject) => {
|
if (isFilePath) {
|
||||||
const fs = wx.getFileSystemManager();
|
return new Promise((resolve, reject) => {
|
||||||
fs.readFile({
|
const fs = wx.getFileSystemManager();
|
||||||
filePath: file,
|
fs.readFile({
|
||||||
success: (fileRes) => {
|
filePath: file,
|
||||||
// 使用 PUT 方法上传
|
encoding: 'binary',
|
||||||
wx.request({
|
success: res => {
|
||||||
url: uploadUrl,
|
resolve(global.fetch(uploadUrl, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
data: fileRes.data, // ArrayBuffer 格式
|
headers: {
|
||||||
header: {
|
'Content-Type': 'application/octet-stream',
|
||||||
'Content-Type': 'image/jpeg', // 根据实际文件类型设置
|
},
|
||||||
},
|
body: res.data,
|
||||||
success: (uploadRes) => {
|
}));
|
||||||
if (uploadRes.statusCode === 200) {
|
},
|
||||||
resolve(uploadRes);
|
fail: err => {
|
||||||
}
|
reject(err);
|
||||||
else {
|
}
|
||||||
reject(new Error(`HTTP Error: ${uploadRes.statusCode}`));
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('上传失败', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('读取文件失败', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return global.fetch(uploadUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
},
|
||||||
|
body: file,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,17 @@
|
||||||
export declare class Upload {
|
import { UploadInterface } from "../types/Upload";
|
||||||
uploadFile(file: File | string, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, method?: "POST" | "PUT" | "PATCH"): Promise<any>;
|
export declare class Upload implements UploadInterface {
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
getActiveUploads(): string[];
|
||||||
|
uploadFile(options: {
|
||||||
|
file: File | string | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
}): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
export class Upload {
|
export class Upload {
|
||||||
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, method = "POST") {
|
abortUpload(uploadId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
abortAllUploads() {
|
||||||
|
}
|
||||||
|
getUploadStatus(uploadId) {
|
||||||
|
return 'not-found';
|
||||||
|
}
|
||||||
|
getActiveUploads() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
async uploadFile(options) {
|
||||||
|
const { file, name, uploadUrl, formData, method = "POST" } = options;
|
||||||
const isPut = method === "PUT";
|
const isPut = method === "PUT";
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
// S3 预签名上传
|
// S3 预签名上传
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,20 @@
|
||||||
export declare class Upload {
|
import { UploadInterface } from "../types/Upload";
|
||||||
uploadFile(file: File | string, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, method?: "POST" | "PUT" | "PATCH"): Promise<any>;
|
export declare class Upload implements UploadInterface {
|
||||||
|
private controllers;
|
||||||
|
constructor();
|
||||||
|
uploadFile(options: {
|
||||||
|
file: File | string | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
}): Promise<any>;
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
private generateUploadId;
|
||||||
|
getActiveUploads(): string[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,33 @@
|
||||||
export class Upload {
|
export class Upload {
|
||||||
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, method = "POST") {
|
controllers = new Map();
|
||||||
|
constructor() {
|
||||||
|
this.uploadFile = this.uploadFile.bind(this);
|
||||||
|
this.abortUpload = this.abortUpload.bind(this);
|
||||||
|
this.abortAllUploads = this.abortAllUploads.bind(this);
|
||||||
|
this.getUploadStatus = this.getUploadStatus.bind(this);
|
||||||
|
this.getActiveUploads = this.getActiveUploads.bind(this);
|
||||||
|
}
|
||||||
|
async uploadFile(options) {
|
||||||
|
const { file, name, uploadUrl, formData, getPercent, uploadId, method = "POST" } = options;
|
||||||
const isPut = method === "PUT";
|
const isPut = method === "PUT";
|
||||||
|
const id = uploadId || this.generateUploadId(file, uploadUrl);
|
||||||
|
// 如果已有相同ID的上传在进行,先中断它
|
||||||
|
if (this.controllers.has(id)) {
|
||||||
|
this.abortUpload(id);
|
||||||
|
}
|
||||||
|
// 创建新的 AbortController
|
||||||
|
const controller = new AbortController();
|
||||||
|
this.controllers.set(id, controller);
|
||||||
// 进度监听模式
|
// 进度监听模式
|
||||||
if (getPercent) {
|
if (getPercent) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
let percent = 0;
|
let percent = 0;
|
||||||
|
// 监听中止信号
|
||||||
|
controller.signal.addEventListener('abort', () => {
|
||||||
|
xhr.abort();
|
||||||
|
reject(new DOMException('Upload aborted', 'AbortError'));
|
||||||
|
});
|
||||||
xhr.upload.addEventListener("progress", (event) => {
|
xhr.upload.addEventListener("progress", (event) => {
|
||||||
if (event.lengthComputable) {
|
if (event.lengthComputable) {
|
||||||
percent = Math.round((event.loaded / event.total) * 100);
|
percent = Math.round((event.loaded / event.total) * 100);
|
||||||
|
|
@ -13,17 +35,34 @@ export class Upload {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
|
this.controllers.delete(id); // 清理控制器
|
||||||
|
// 构造类似 Response 的对象,支持 headers.get()
|
||||||
|
const headersMap = new Map();
|
||||||
|
const headersStr = xhr.getAllResponseHeaders();
|
||||||
|
headersStr.split('\r\n').forEach(line => {
|
||||||
|
const parts = line.split(': ');
|
||||||
|
if (parts.length === 2) {
|
||||||
|
headersMap.set(parts[0].toLowerCase(), parts[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const headers = {
|
||||||
|
get: (name) => headersMap.get(name.toLowerCase()) || null,
|
||||||
|
has: (name) => headersMap.has(name.toLowerCase()),
|
||||||
|
forEach: (callback) => {
|
||||||
|
headersMap.forEach(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
if (xhr.status === 204) {
|
if (xhr.status === 204) {
|
||||||
resolve({ status: 204 });
|
resolve({ status: 204, headers });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(xhr.responseText);
|
const data = JSON.parse(xhr.responseText);
|
||||||
resolve(data);
|
resolve({ ...data, status: xhr.status, headers });
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
resolve({ status: xhr.status, raw: xhr.responseText });
|
resolve({ status: xhr.status, raw: xhr.responseText, headers });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -31,13 +70,28 @@ export class Upload {
|
||||||
reject(new Error(`HTTP Error: ${xhr.status}`));
|
reject(new Error(`HTTP Error: ${xhr.status}`));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.onerror = () => reject(new Error("Network Error"));
|
xhr.onerror = () => {
|
||||||
|
this.controllers.delete(id);
|
||||||
|
// 如果不是因为中止导致的错误
|
||||||
|
if (!controller.signal.aborted) {
|
||||||
|
reject(new Error("Network Error"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onabort = () => {
|
||||||
|
this.controllers.delete(id);
|
||||||
|
if (controller.signal.aborted) {
|
||||||
|
reject(new DOMException('Upload aborted', 'AbortError'));
|
||||||
|
}
|
||||||
|
};
|
||||||
xhr.open(method, uploadUrl);
|
xhr.open(method, uploadUrl);
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
// PUT 模式:直接上传文件
|
// PUT 模式:直接上传文件
|
||||||
if (file instanceof File) {
|
if (file instanceof File) {
|
||||||
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
||||||
}
|
}
|
||||||
|
else if (file instanceof Blob) {
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
||||||
|
}
|
||||||
xhr.send(file);
|
xhr.send(file);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -52,31 +106,84 @@ export class Upload {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 无进度监听模式(直接 fetch)
|
// 无进度监听模式(直接 fetch)
|
||||||
if (isPut) {
|
try {
|
||||||
// S3 预签名上传
|
let result;
|
||||||
const headers = {};
|
if (isPut) {
|
||||||
if (file instanceof File) {
|
// S3 预签名上传
|
||||||
headers["Content-Type"] = file.type || "application/octet-stream";
|
const headers = {};
|
||||||
|
if (file instanceof File) {
|
||||||
|
headers["Content-Type"] = file.type || "application/octet-stream";
|
||||||
|
}
|
||||||
|
else if (file instanceof Blob) {
|
||||||
|
headers["Content-Type"] = "application/octet-stream";
|
||||||
|
}
|
||||||
|
result = await fetch(uploadUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
headers,
|
||||||
|
body: file,
|
||||||
|
signal: controller.signal, // 添加中止信号
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const result = await fetch(uploadUrl, {
|
else {
|
||||||
method: "PUT",
|
// 表单上传
|
||||||
headers,
|
const formData2 = new FormData();
|
||||||
body: file,
|
for (const key of Object.keys(formData)) {
|
||||||
});
|
formData2.append(key, formData[key]);
|
||||||
|
}
|
||||||
|
formData2.append(name || "file", file);
|
||||||
|
result = await fetch(uploadUrl, {
|
||||||
|
method,
|
||||||
|
body: formData2,
|
||||||
|
signal: controller.signal, // 添加中止信号
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.controllers.delete(id); // 成功后清理控制器
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
else {
|
catch (error) {
|
||||||
// 表单上传
|
this.controllers.delete(id); // 失败后清理控制器
|
||||||
const formData2 = new FormData();
|
// 人为中断返回204 使general-business处理成功
|
||||||
for (const key of Object.keys(formData)) {
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||||
formData2.append(key, formData[key]);
|
throw new DOMException('Upload aborted', 'AbortError');
|
||||||
}
|
}
|
||||||
formData2.append(name || "file", file);
|
throw error;
|
||||||
const result = await fetch(uploadUrl, {
|
|
||||||
method,
|
|
||||||
body: formData2,
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 中断特定上传
|
||||||
|
abortUpload(uploadId) {
|
||||||
|
const controller = this.controllers.get(uploadId);
|
||||||
|
if (controller) {
|
||||||
|
controller.abort();
|
||||||
|
this.controllers.delete(uploadId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 中断所有上传
|
||||||
|
abortAllUploads() {
|
||||||
|
this.controllers.forEach((controller, id) => {
|
||||||
|
controller.abort();
|
||||||
|
});
|
||||||
|
this.controllers.clear();
|
||||||
|
}
|
||||||
|
// 获取上传状态
|
||||||
|
getUploadStatus(uploadId) {
|
||||||
|
const controller = this.controllers.get(uploadId);
|
||||||
|
if (!controller)
|
||||||
|
return 'not-found';
|
||||||
|
if (controller.signal.aborted)
|
||||||
|
return 'aborted';
|
||||||
|
return 'uploading';
|
||||||
|
}
|
||||||
|
// 生成唯一的上传ID
|
||||||
|
generateUploadId(file, uploadUrl) {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const random = Math.random().toString(36).substring(2, 9);
|
||||||
|
const fileInfo = file instanceof File ? `${file.name}-${file.size}` : file instanceof Blob ? `blob-${file.size}` : file;
|
||||||
|
return `${uploadUrl}-${fileInfo}-${timestamp}-${random}`;
|
||||||
|
}
|
||||||
|
// 获取所有进行中的上传任务
|
||||||
|
getActiveUploads() {
|
||||||
|
return Array.from(this.controllers.keys());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,13 +78,12 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
wx.navigateTo({
|
wx.navigateTo({
|
||||||
url: url,
|
url: url,
|
||||||
success: () => {
|
success: () => {
|
||||||
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.publish();
|
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 关闭当前页面,跳转到应用内的某个页面,但不允许跳转到tabBar页面。
|
// 关闭当前页面,跳转到应用内的某个页面,但不允许跳转到tabBar页面。
|
||||||
|
|
@ -97,12 +96,12 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
wx.redirectTo({
|
wx.redirectTo({
|
||||||
url: url,
|
url: url,
|
||||||
success: () => {
|
success: () => {
|
||||||
resolve(undefined);
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//跳转到tabBar页面,并关闭其他所有非tabBar页面,用于跳转到主页。
|
//跳转到tabBar页面,并关闭其他所有非tabBar页面,用于跳转到主页。
|
||||||
|
|
@ -115,12 +114,12 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
wx.switchTab({
|
wx.switchTab({
|
||||||
url: url,
|
url: url,
|
||||||
success: () => {
|
success: () => {
|
||||||
resolve(undefined);
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
navigateBack(delta) {
|
navigateBack(delta) {
|
||||||
|
|
@ -131,29 +130,23 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
wx.navigateBack({
|
wx.navigateBack({
|
||||||
delta: delta || 1,
|
delta: delta || 1,
|
||||||
success: () => {
|
success: () => {
|
||||||
resolve(undefined);
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
||||||
if (!this.enter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pages = getCurrentPages();
|
const pages = getCurrentPages();
|
||||||
if (pages.length > 1) {
|
if (pages.length > 1) {
|
||||||
this.leave();
|
|
||||||
return this.navigateBack();
|
return this.navigateBack();
|
||||||
}
|
}
|
||||||
const isTabBar = options?.isTabBar;
|
const isTabBar = options?.isTabBar;
|
||||||
if (isTabBar) {
|
if (isTabBar) {
|
||||||
this.leave();
|
|
||||||
return this.switchTab(options, state, disableNamespace);
|
return this.switchTab(options, state, disableNamespace);
|
||||||
}
|
}
|
||||||
this.leave();
|
|
||||||
return this.redirectTo(options, state, disableNamespace);
|
return this.redirectTo(options, state, disableNamespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,8 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
||||||
const replaceAction = native_1.StackActions.replace(url, props);
|
const replaceAction = native_1.StackActions.replace(url, props);
|
||||||
this.history.dispatch(replaceAction);
|
this.history.dispatch(replaceAction);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
async switchTab(options, state, disableNamespace) {
|
async switchTab(options, state, disableNamespace) {
|
||||||
if (!this.enter()) {
|
if (!this.enter()) {
|
||||||
|
|
@ -69,8 +69,8 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
||||||
const jumpToAction = native_1.TabActions.jumpTo(url, props);
|
const jumpToAction = native_1.TabActions.jumpTo(url, props);
|
||||||
this.history.dispatch(jumpToAction);
|
this.history.dispatch(jumpToAction);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
async navigateBack(delta) {
|
async navigateBack(delta) {
|
||||||
if (!this.enter()) {
|
if (!this.enter()) {
|
||||||
|
|
@ -80,9 +80,12 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
if (canGoBack) {
|
if (canGoBack) {
|
||||||
const popAction = native_1.StackActions.pop(delta || 1);
|
const popAction = native_1.StackActions.pop(delta || 1);
|
||||||
this.history.dispatch(popAction);
|
this.history.dispatch(popAction);
|
||||||
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
}
|
}
|
||||||
this.leave();
|
else {
|
||||||
|
this.leave();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
||||||
if (!this.enter()) {
|
if (!this.enter()) {
|
||||||
|
|
@ -91,14 +94,14 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
const canGoBack = this.history.canGoBack();
|
const canGoBack = this.history.canGoBack();
|
||||||
if (canGoBack) {
|
if (canGoBack) {
|
||||||
this.navigateBack();
|
this.navigateBack();
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 回最顶层
|
// 回最顶层
|
||||||
this.history.dispatch(native_1.StackActions.popToTop());
|
this.history.dispatch(native_1.StackActions.popToTop());
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.Navigator = Navigator;
|
exports.Navigator = Navigator;
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
}
|
}
|
||||||
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
||||||
this.history.push(url, props);
|
this.history.push(url, props);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
async redirectTo(options, state, disableNamespace) {
|
async redirectTo(options, state, disableNamespace) {
|
||||||
if (!this.enter()) {
|
if (!this.enter()) {
|
||||||
|
|
@ -57,8 +57,8 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
}
|
}
|
||||||
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
const { url, props } = this.getUrlAndProps(options, state, disableNamespace);
|
||||||
this.history.replace(url, props);
|
this.history.replace(url, props);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
async switchTab(options, state, disableNamespace) {
|
async switchTab(options, state, disableNamespace) {
|
||||||
console.error('浏览器无switchTab');
|
console.error('浏览器无switchTab');
|
||||||
|
|
@ -71,8 +71,8 @@ class Navigator extends navigator_common_1.Navigator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.history.go(delta ? 0 - delta : -1);
|
this.history.go(delta ? 0 - delta : -1);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
navigateBackOrRedirectTo(options, state, disableNamespace) {
|
||||||
console.error('浏览器暂无法获得history堆栈');
|
console.error('浏览器暂无法获得history堆栈');
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ declare class SingleNode<ED extends EntityDict & BaseEntityDict, T extends keyof
|
||||||
saveRefreshResult(data: Record<string, any>): void;
|
saveRefreshResult(data: Record<string, any>): void;
|
||||||
refresh(): Promise<void>;
|
refresh(): Promise<void>;
|
||||||
clean(lsn?: number, dontPublish?: true): void;
|
clean(lsn?: number, dontPublish?: true): void;
|
||||||
private getFilter;
|
getFilter(ignoreNew?: true, onlyHot?: true): ED[T]['Filter'] | undefined;
|
||||||
getIntrinsticFilters(): ED[T]["Filter"] | undefined;
|
getIntrinsticFilters(): ED[T]["Filter"] | undefined;
|
||||||
/**
|
/**
|
||||||
* getParentFilter不能假设一定已经有数据,只能根据当前filter的条件去构造
|
* getParentFilter不能假设一定已经有数据,只能根据当前filter的条件去构造
|
||||||
|
|
|
||||||
|
|
@ -540,14 +540,14 @@ class ListNode extends EntityNode {
|
||||||
/** 检查原本就在的,现在还在不在,
|
/** 检查原本就在的,现在还在不在,
|
||||||
* 以及原本不在的,现在是不是满足条件了
|
* 以及原本不在的,现在是不是满足条件了
|
||||||
* (后一种情况也可能原本就满足但不在当前的ids中,这时是判断不出来的,total+1可能不对,但是应该是较少的case)*/
|
* (后一种情况也可能原本就满足但不在当前的ids中,这时是判断不出来的,total+1可能不对,但是应该是较少的case)*/
|
||||||
const filter = this.constructFilters(true, true, true);
|
const filters = this.constructFilters(true, true, true);
|
||||||
if (intersected.length) {
|
if (intersected.length) {
|
||||||
const rows = this.cache.get(this.entity, {
|
const rows = this.cache.get(this.entity, {
|
||||||
data: {
|
data: {
|
||||||
id: 1,
|
id: 1,
|
||||||
},
|
},
|
||||||
filter: (0, filter_1.combineFilters)(this.entity, this.schema, [
|
filter: (0, filter_1.combineFilters)(this.entity, this.schema, [
|
||||||
...(filter || []),
|
...(filters || []),
|
||||||
{
|
{
|
||||||
id: {
|
id: {
|
||||||
$in: intersected,
|
$in: intersected,
|
||||||
|
|
@ -565,12 +565,8 @@ class ListNode extends EntityNode {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (diffed.length) {
|
if (diffed.length) {
|
||||||
diffed.forEach((ele) => {
|
const filter = filters && (0, filter_1.combineFilters)(e, this.schema, filters);
|
||||||
this.sr[ele] = {};
|
diffed.forEach((ele) => tryAddRowToList(ele, filter));
|
||||||
if (this.pagination.total) {
|
|
||||||
this.pagination.total++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// hasUpdated = true;
|
// hasUpdated = true;
|
||||||
|
|
@ -1259,7 +1255,7 @@ class SingleNode extends EntityNode {
|
||||||
setId(id) {
|
setId(id) {
|
||||||
if (id !== this.id) {
|
if (id !== this.id) {
|
||||||
const operations = this.ulManager.makeOperations();
|
const operations = this.ulManager.makeOperations();
|
||||||
(0, assert_1.assert)(operations.length <= 1);
|
(0, assert_1.assert)(operations.length <= 1, 'singleNode在setId时出现数据不一致');
|
||||||
const [operation] = operations;
|
const [operation] = operations;
|
||||||
if (operation?.action === 'create') {
|
if (operation?.action === 'create') {
|
||||||
if (operation.data.id === id) {
|
if (operation.data.id === id) {
|
||||||
|
|
@ -1420,7 +1416,7 @@ class SingleNode extends EntityNode {
|
||||||
}
|
}
|
||||||
setDirty() {
|
setDirty() {
|
||||||
const id = this.getId();
|
const id = this.getId();
|
||||||
(0, assert_1.assert)(id);
|
(0, assert_1.assert)(id, "不能对没有id的singleNode设置dirty");
|
||||||
this.ulManager.push(this.ulManager.maxLsn, {
|
this.ulManager.push(this.ulManager.maxLsn, {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
data: {},
|
data: {},
|
||||||
|
|
@ -1448,7 +1444,7 @@ class SingleNode extends EntityNode {
|
||||||
const childOperations = child.composeOperations(paths?.length ? paths : undefined);
|
const childOperations = child.composeOperations(paths?.length ? paths : undefined);
|
||||||
if (childOperations) {
|
if (childOperations) {
|
||||||
if (child instanceof SingleNode) {
|
if (child instanceof SingleNode) {
|
||||||
(0, assert_1.assert)(childOperations.length === 1);
|
(0, assert_1.assert)(childOperations.length === 1, 'singleNode在composeOperations时出现数据不一致');
|
||||||
this.ulManager.push(lsnMax + 100, {
|
this.ulManager.push(lsnMax + 100, {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -1460,7 +1456,7 @@ class SingleNode extends EntityNode {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
(0, assert_1.assert)(child instanceof ListNode);
|
(0, assert_1.assert)(child instanceof ListNode, 'child必须是ListNode');
|
||||||
this.ulManager.push(lsnMax + 100, {
|
this.ulManager.push(lsnMax + 100, {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -1550,8 +1546,8 @@ class SingleNode extends EntityNode {
|
||||||
const rel = this.judgeRelation(k2);
|
const rel = this.judgeRelation(k2);
|
||||||
if (rel === 2) {
|
if (rel === 2) {
|
||||||
if (value?.entityId) {
|
if (value?.entityId) {
|
||||||
(0, assert_1.assert)(child instanceof SingleNode);
|
(0, assert_1.assert)(child instanceof SingleNode, 'child必须是singleNode');
|
||||||
(0, assert_1.assert)(value.entity === child.getEntity());
|
(0, assert_1.assert)(value.entity === child.getEntity(), 'singleNode的entity必须一致');
|
||||||
child.saveRefreshResult({
|
child.saveRefreshResult({
|
||||||
[value.entityId]: this.sr[k2] || {},
|
[value.entityId]: this.sr[k2] || {},
|
||||||
});
|
});
|
||||||
|
|
@ -2014,6 +2010,7 @@ class RunningTree extends Feature_1.Feature {
|
||||||
(0, assert_1.assert)(!parentNode || parentNode instanceof VirtualNode);
|
(0, assert_1.assert)(!parentNode || parentNode instanceof VirtualNode);
|
||||||
node = new VirtualNode(fullPath, path, parentNode, stale);
|
node = new VirtualNode(fullPath, path, parentNode, stale);
|
||||||
}
|
}
|
||||||
|
// 任何临时的修改都需要回滚,上面缓存了一些update操作
|
||||||
rollback();
|
rollback();
|
||||||
if (!parentNode) {
|
if (!parentNode) {
|
||||||
(0, assert_1.assert)(!parent && !this.root[path]);
|
(0, assert_1.assert)(!parent && !this.root[path]);
|
||||||
|
|
@ -2420,7 +2417,7 @@ class RunningTree extends Feature_1.Feature {
|
||||||
const node = path && this.findNode(path);
|
const node = path && this.findNode(path);
|
||||||
// assert(node.isDirty());
|
// assert(node.isDirty());
|
||||||
node && node.setExecuting(true);
|
node && node.setExecuting(true);
|
||||||
let pollute = false;
|
// let pollute = false;
|
||||||
try {
|
try {
|
||||||
let operations = path && this.getOperations(path) || [];
|
let operations = path && this.getOperations(path) || [];
|
||||||
if (opers) {
|
if (opers) {
|
||||||
|
|
@ -2439,15 +2436,30 @@ class RunningTree extends Feature_1.Feature {
|
||||||
else if (node) {
|
else if (node) {
|
||||||
// 老的写法,直接对一个非脏的结点execute某个action,也可以支持
|
// 老的写法,直接对一个非脏的结点execute某个action,也可以支持
|
||||||
(0, assert_1.assert)(node instanceof SingleNode);
|
(0, assert_1.assert)(node instanceof SingleNode);
|
||||||
node.update(this.logSerailNumber, {}, action);
|
// node.update(this.logSerailNumber, {}, action);
|
||||||
pollute = true;
|
// pollute = true;
|
||||||
operations = node.composeOperations() || [];
|
// operations = node.composeOperations() || [];
|
||||||
(0, assert_1.assert)(operations.length === 1);
|
// assert(operations.length === 1);
|
||||||
const [operation1] = operations;
|
// const [operation1] = operations;
|
||||||
if (action !== operation1.operation.action) {
|
// if (action !== operation1.operation.action) {
|
||||||
(0, assert_1.assert)(operation1.operation.action === 'update'); // 如果execute时传action,前面update动作应该只可能是update
|
// assert(operation1.operation.action === 'update'); // 如果execute时传action,前面update动作应该只可能是update
|
||||||
operation1.operation.action = action;
|
// operation1.operation.action = action;
|
||||||
}
|
// }
|
||||||
|
/**
|
||||||
|
* 上述写法会触发publish行为,如:一个close动作会导致页面像下面这样渲染:
|
||||||
|
* opened --> closed(上面的node.update) --> opened(执行成功回来setExecuting时) --> closed(执行成功回来再sync cache后)
|
||||||
|
*/
|
||||||
|
operations.push({
|
||||||
|
entity: node.getEntity(),
|
||||||
|
operation: {
|
||||||
|
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||||
|
action,
|
||||||
|
data: {
|
||||||
|
$$updateAt$$: Date.now(),
|
||||||
|
},
|
||||||
|
filter: node.getFilter(true),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (operations.length > 0) {
|
if (operations.length > 0) {
|
||||||
|
|
@ -2477,9 +2489,9 @@ class RunningTree extends Feature_1.Feature {
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
node && node.setExecuting(false);
|
node && node.setExecuting(false);
|
||||||
if (pollute) {
|
/* if (pollute) {
|
||||||
path && this.clean(path);
|
path && this.clean(path);
|
||||||
}
|
} */
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,11 +98,11 @@ class SubScriber extends Feature_1.Feature {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
socket.on('data', (opRecords, event) => {
|
socket.on('data', (opRecords, event) => {
|
||||||
|
this.cache.sync(opRecords);
|
||||||
const registered = this.eventMap[event];
|
const registered = this.eventMap[event];
|
||||||
if (registered) {
|
if (registered) {
|
||||||
registered.callbacks.forEach((ele) => ele(event, opRecords));
|
registered.callbacks.forEach((ele) => ele(event, opRecords));
|
||||||
}
|
}
|
||||||
this.cache.sync(opRecords);
|
|
||||||
});
|
});
|
||||||
socket.on('error', (errString) => {
|
socket.on('error', (errString) => {
|
||||||
console.error(errString);
|
console.error(errString);
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@ import { Feature } from './types/Feature';
|
||||||
import { DataOption, OakComponentOption } from './types/Page';
|
import { DataOption, OakComponentOption } from './types/Page';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends DataOption = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): string;
|
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends DataOption = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): string;
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,13 @@ function translatePropertiesToPropertyDefinitions(properties) {
|
||||||
const definitions = {};
|
const definitions = {};
|
||||||
if (properties) {
|
if (properties) {
|
||||||
Object.keys(properties).forEach((prop) => {
|
Object.keys(properties).forEach((prop) => {
|
||||||
|
if (properties[prop] === null) {
|
||||||
|
definitions[prop] = {
|
||||||
|
type: null,
|
||||||
|
value: null,
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (typeof properties[prop]) {
|
switch (typeof properties[prop]) {
|
||||||
case 'string': {
|
case 'string': {
|
||||||
if (properties[prop]) {
|
if (properties[prop]) {
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ import { Feature } from './types/Feature';
|
||||||
import { DataOption, OakComponentOption } from './types/Page';
|
import { DataOption, OakComponentOption } from './types/Page';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
|
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { MessageProps } from './types/Message';
|
||||||
import { NotificationProps } from './types/Notification';
|
import { NotificationProps } from './types/Notification';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): {
|
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): {
|
||||||
new (props: ComponentProps<ED, T, TProperty>): {
|
new (props: ComponentProps<ED, T, TProperty>): {
|
||||||
features: BasicFeatures<ED> & FD;
|
features: BasicFeatures<ED> & FD;
|
||||||
oakOption: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>;
|
oakOption: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>;
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ import { Feature } from './types/Feature';
|
||||||
import { DataOption, OakComponentOption } from './types/Page';
|
import { DataOption, OakComponentOption } from './types/Page';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
|
export declare function createComponent<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any> = {}, TProperty extends DataOption = {}, TMethod extends Record<string, Function> = {}>(option: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>, features: BasicFeatures<ED> & FD): React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ export type ComponentFullThisType<ED extends EntityDict & BaseEntityDict, T exte
|
||||||
triggerEvent: <DetailType = any>(name: string, detail?: DetailType, options?: WechatMiniprogram.Component.TriggerEventOption) => void;
|
triggerEvent: <DetailType = any>(name: string, detail?: DetailType, options?: WechatMiniprogram.Component.TriggerEventOption) => void;
|
||||||
oakLifetime: OakLifetime;
|
oakLifetime: OakLifetime;
|
||||||
} & OakCommonComponentMethods<ED, T> & OakListComponentMethods<ED, T> & OakSingleComponentMethods<ED, T>;
|
} & OakCommonComponentMethods<ED, T> & OakListComponentMethods<ED, T> & OakSingleComponentMethods<ED, T>;
|
||||||
export type OakComponentOption<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any>, TProperty extends DataOption, TMethod extends Record<string, Function>, EMethod extends Record<string, Function> = {}> = ComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod, EMethod> & Partial<{
|
export type OakComponentOption<IsList extends boolean, ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>, FormedData extends Record<string, any>, TData extends Record<string, any>, TProperty extends DataOption, TMethod extends Record<string, Function>, EMethod extends Record<string, Function> = {}> = ComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod, EMethod> & Partial<{
|
||||||
/**
|
/**
|
||||||
* 生命周期回调
|
* 生命周期回调
|
||||||
*/
|
*/
|
||||||
|
|
@ -351,7 +351,7 @@ export type OakComponentData<ED extends EntityDict & BaseEntityDict, T extends k
|
||||||
type OakListComoponetData<ED extends EntityDict & BaseEntityDict, T extends keyof ED> = {
|
type OakListComoponetData<ED extends EntityDict & BaseEntityDict, T extends keyof ED> = {
|
||||||
oakPagination?: Pagination;
|
oakPagination?: Pagination;
|
||||||
};
|
};
|
||||||
export type MakeOakComponent<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, AsyncContext<ED>>>, FD extends Record<string, Feature>> = <IsList extends boolean, T extends keyof ED, FormedData extends DataOption, TData extends DataOption, TProperty extends DataOption, TMethod extends MethodOption>(options: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>) => (props: ReactComponentProps<ED, T, IsList, TProperty>) => React.ReactElement;
|
export type MakeOakComponent<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends Record<string, Aspect<ED, Cxt>>, FD extends Record<string, Feature>> = <IsList extends boolean, T extends keyof ED, FormedData extends DataOption, TData extends DataOption, TProperty extends DataOption, TMethod extends MethodOption>(options: OakComponentOption<IsList, ED, T, Cxt, FrontCxt, AD, FD, FormedData, TData, TProperty, TMethod>) => (props: ReactComponentProps<ED, T, IsList, TProperty>) => React.ReactElement;
|
||||||
export type WebComponentCommonMethodNames = 'setNotification' | 'setMessage' | 'navigateTo' | 'navigateBack' | 'redirectTo' | 'clean' | 't' | 'execute' | 'refresh' | 'aggregate' | 'checkOperation' | 'isDirty';
|
export type WebComponentCommonMethodNames = 'setNotification' | 'setMessage' | 'navigateTo' | 'navigateBack' | 'redirectTo' | 'clean' | 't' | 'execute' | 'refresh' | 'aggregate' | 'checkOperation' | 'isDirty';
|
||||||
export type WebComponentListMethodNames = 'loadMore' | 'setFilters' | 'addNamedFilter' | 'removeNamedFilter' | 'removeNamedFilterByName' | 'setNamedSorters' | 'addNamedSorter' | 'removeNamedSorter' | 'removeNamedSorterByName' | 'setPageSize' | 'setCurrentPage' | 'addItem' | 'addItems' | 'removeItem' | 'removeItems' | 'updateItem' | 'updateItems' | 'resetItem' | 'recoverItem' | 'recoverItems';
|
export type WebComponentListMethodNames = 'loadMore' | 'setFilters' | 'addNamedFilter' | 'removeNamedFilter' | 'removeNamedFilterByName' | 'setNamedSorters' | 'addNamedSorter' | 'removeNamedSorter' | 'removeNamedSorterByName' | 'setPageSize' | 'setCurrentPage' | 'addItem' | 'addItems' | 'removeItem' | 'removeItems' | 'updateItem' | 'updateItems' | 'resetItem' | 'recoverItem' | 'recoverItems';
|
||||||
export type WebComponentSingleMethodNames = 'update' | 'remove' | 'create' | 'isCreation' | 'getId' | 'setId';
|
export type WebComponentSingleMethodNames = 'update' | 'remove' | 'create' | 'isCreation' | 'getId' | 'setId';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
type UploadFileFn = (options: {
|
||||||
|
file: string | File | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
isFilePath?: boolean;
|
||||||
|
}) => Promise<{
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
statusCode?: number;
|
||||||
|
headers: {
|
||||||
|
get(name: string): string | null;
|
||||||
|
};
|
||||||
|
json(): Promise<any>;
|
||||||
|
text(): Promise<string>;
|
||||||
|
errMsg?: string;
|
||||||
|
data?: any;
|
||||||
|
}>;
|
||||||
|
export interface UploadInterface {
|
||||||
|
uploadFile: UploadFileFn;
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
getActiveUploads(): string[];
|
||||||
|
}
|
||||||
|
export {};
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
@ -1,3 +1,21 @@
|
||||||
export declare class Upload {
|
import { UploadInterface } from "../types/Upload";
|
||||||
uploadFile(file: string | File, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean): Promise<any>;
|
export declare class Upload implements UploadInterface {
|
||||||
|
uploadFile(options: {
|
||||||
|
file: string | File | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
isFilePath?: boolean;
|
||||||
|
}): Promise<any>;
|
||||||
|
private controllers;
|
||||||
|
constructor();
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
private generateUploadId;
|
||||||
|
getActiveUploads(): string[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,38 @@
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.Upload = void 0;
|
exports.Upload = void 0;
|
||||||
class Upload {
|
class Upload {
|
||||||
async uploadFile(file, name, uploadUrl, formData, autoInform) {
|
async uploadFile(options) {
|
||||||
console.warn('server不会调用此函数');
|
console.warn('server不会调用此函数');
|
||||||
}
|
}
|
||||||
|
controllers = new Map();
|
||||||
|
constructor() {
|
||||||
|
this.uploadFile = this.uploadFile.bind(this);
|
||||||
|
this.abortUpload = this.abortUpload.bind(this);
|
||||||
|
this.abortAllUploads = this.abortAllUploads.bind(this);
|
||||||
|
this.getUploadStatus = this.getUploadStatus.bind(this);
|
||||||
|
this.getActiveUploads = this.getActiveUploads.bind(this);
|
||||||
|
}
|
||||||
|
// 中断特定上传
|
||||||
|
abortUpload(uploadId) {
|
||||||
|
console.warn('server不会调用此函数');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 中断所有上传
|
||||||
|
abortAllUploads() {
|
||||||
|
console.warn('server不会调用此函数');
|
||||||
|
}
|
||||||
|
// 获取上传状态
|
||||||
|
getUploadStatus(uploadId) {
|
||||||
|
console.warn('server不会调用此函数');
|
||||||
|
return 'uploading';
|
||||||
|
}
|
||||||
|
// 生成唯一的上传ID
|
||||||
|
generateUploadId(file, uploadUrl) {
|
||||||
|
return `server不会调用此函数`;
|
||||||
|
}
|
||||||
|
// 获取所有进行中的上传任务
|
||||||
|
getActiveUploads() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exports.Upload = Upload;
|
exports.Upload = Upload;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,18 @@
|
||||||
export declare class Upload {
|
import { UploadInterface } from '../types/Upload';
|
||||||
uploadFile(file: string | File, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, method?: "POST" | "PUT" | "PATCH"): Promise<any>;
|
export declare class Upload implements UploadInterface {
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
getActiveUploads(): string[];
|
||||||
|
uploadFile(options: {
|
||||||
|
file: string | File | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
isFilePath?: boolean;
|
||||||
|
}): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,41 +3,48 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.Upload = void 0;
|
exports.Upload = void 0;
|
||||||
const promisify_1 = require("./promisify");
|
const promisify_1 = require("./promisify");
|
||||||
class Upload {
|
class Upload {
|
||||||
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, method = "POST") {
|
abortUpload(uploadId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
abortAllUploads() {
|
||||||
|
}
|
||||||
|
getUploadStatus(uploadId) {
|
||||||
|
return 'not-found';
|
||||||
|
}
|
||||||
|
getActiveUploads() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
async uploadFile(options) {
|
||||||
|
const { file, name, uploadUrl, formData, isFilePath, method = "POST" } = options;
|
||||||
const isPut = method === "PUT";
|
const isPut = method === "PUT";
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
return new Promise((resolve, reject) => {
|
if (isFilePath) {
|
||||||
const fs = wx.getFileSystemManager();
|
return new Promise((resolve, reject) => {
|
||||||
fs.readFile({
|
const fs = wx.getFileSystemManager();
|
||||||
filePath: file,
|
fs.readFile({
|
||||||
success: (fileRes) => {
|
filePath: file,
|
||||||
// 使用 PUT 方法上传
|
encoding: 'binary',
|
||||||
wx.request({
|
success: res => {
|
||||||
url: uploadUrl,
|
resolve(global.fetch(uploadUrl, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
data: fileRes.data, // ArrayBuffer 格式
|
headers: {
|
||||||
header: {
|
'Content-Type': 'application/octet-stream',
|
||||||
'Content-Type': 'image/jpeg', // 根据实际文件类型设置
|
},
|
||||||
},
|
body: res.data,
|
||||||
success: (uploadRes) => {
|
}));
|
||||||
if (uploadRes.statusCode === 200) {
|
},
|
||||||
resolve(uploadRes);
|
fail: err => {
|
||||||
}
|
reject(err);
|
||||||
else {
|
}
|
||||||
reject(new Error(`HTTP Error: ${uploadRes.statusCode}`));
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('上传失败', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('读取文件失败', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return global.fetch(uploadUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
},
|
||||||
|
body: file,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,17 @@
|
||||||
export declare class Upload {
|
import { UploadInterface } from "../types/Upload";
|
||||||
uploadFile(file: File | string, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, method?: "POST" | "PUT" | "PATCH"): Promise<any>;
|
export declare class Upload implements UploadInterface {
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
getActiveUploads(): string[];
|
||||||
|
uploadFile(options: {
|
||||||
|
file: File | string | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
}): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,19 @@
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.Upload = void 0;
|
exports.Upload = void 0;
|
||||||
class Upload {
|
class Upload {
|
||||||
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, method = "POST") {
|
abortUpload(uploadId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
abortAllUploads() {
|
||||||
|
}
|
||||||
|
getUploadStatus(uploadId) {
|
||||||
|
return 'not-found';
|
||||||
|
}
|
||||||
|
getActiveUploads() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
async uploadFile(options) {
|
||||||
|
const { file, name, uploadUrl, formData, method = "POST" } = options;
|
||||||
const isPut = method === "PUT";
|
const isPut = method === "PUT";
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
// S3 预签名上传
|
// S3 预签名上传
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,20 @@
|
||||||
export declare class Upload {
|
import { UploadInterface } from "../types/Upload";
|
||||||
uploadFile(file: File | string, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, method?: "POST" | "PUT" | "PATCH"): Promise<any>;
|
export declare class Upload implements UploadInterface {
|
||||||
|
private controllers;
|
||||||
|
constructor();
|
||||||
|
uploadFile(options: {
|
||||||
|
file: File | string | Blob;
|
||||||
|
name: string;
|
||||||
|
uploadUrl: string;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
autoInform?: boolean;
|
||||||
|
getPercent?: Function;
|
||||||
|
uploadId?: string;
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
}): Promise<any>;
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
private generateUploadId;
|
||||||
|
getActiveUploads(): string[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,35 @@
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.Upload = void 0;
|
exports.Upload = void 0;
|
||||||
class Upload {
|
class Upload {
|
||||||
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, method = "POST") {
|
controllers = new Map();
|
||||||
|
constructor() {
|
||||||
|
this.uploadFile = this.uploadFile.bind(this);
|
||||||
|
this.abortUpload = this.abortUpload.bind(this);
|
||||||
|
this.abortAllUploads = this.abortAllUploads.bind(this);
|
||||||
|
this.getUploadStatus = this.getUploadStatus.bind(this);
|
||||||
|
this.getActiveUploads = this.getActiveUploads.bind(this);
|
||||||
|
}
|
||||||
|
async uploadFile(options) {
|
||||||
|
const { file, name, uploadUrl, formData, getPercent, uploadId, method = "POST" } = options;
|
||||||
const isPut = method === "PUT";
|
const isPut = method === "PUT";
|
||||||
|
const id = uploadId || this.generateUploadId(file, uploadUrl);
|
||||||
|
// 如果已有相同ID的上传在进行,先中断它
|
||||||
|
if (this.controllers.has(id)) {
|
||||||
|
this.abortUpload(id);
|
||||||
|
}
|
||||||
|
// 创建新的 AbortController
|
||||||
|
const controller = new AbortController();
|
||||||
|
this.controllers.set(id, controller);
|
||||||
// 进度监听模式
|
// 进度监听模式
|
||||||
if (getPercent) {
|
if (getPercent) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
let percent = 0;
|
let percent = 0;
|
||||||
|
// 监听中止信号
|
||||||
|
controller.signal.addEventListener('abort', () => {
|
||||||
|
xhr.abort();
|
||||||
|
reject(new DOMException('Upload aborted', 'AbortError'));
|
||||||
|
});
|
||||||
xhr.upload.addEventListener("progress", (event) => {
|
xhr.upload.addEventListener("progress", (event) => {
|
||||||
if (event.lengthComputable) {
|
if (event.lengthComputable) {
|
||||||
percent = Math.round((event.loaded / event.total) * 100);
|
percent = Math.round((event.loaded / event.total) * 100);
|
||||||
|
|
@ -16,17 +38,34 @@ class Upload {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
|
this.controllers.delete(id); // 清理控制器
|
||||||
|
// 构造类似 Response 的对象,支持 headers.get()
|
||||||
|
const headersMap = new Map();
|
||||||
|
const headersStr = xhr.getAllResponseHeaders();
|
||||||
|
headersStr.split('\r\n').forEach(line => {
|
||||||
|
const parts = line.split(': ');
|
||||||
|
if (parts.length === 2) {
|
||||||
|
headersMap.set(parts[0].toLowerCase(), parts[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const headers = {
|
||||||
|
get: (name) => headersMap.get(name.toLowerCase()) || null,
|
||||||
|
has: (name) => headersMap.has(name.toLowerCase()),
|
||||||
|
forEach: (callback) => {
|
||||||
|
headersMap.forEach(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
if (xhr.status === 204) {
|
if (xhr.status === 204) {
|
||||||
resolve({ status: 204 });
|
resolve({ status: 204, headers });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(xhr.responseText);
|
const data = JSON.parse(xhr.responseText);
|
||||||
resolve(data);
|
resolve({ ...data, status: xhr.status, headers });
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
resolve({ status: xhr.status, raw: xhr.responseText });
|
resolve({ status: xhr.status, raw: xhr.responseText, headers });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,13 +73,28 @@ class Upload {
|
||||||
reject(new Error(`HTTP Error: ${xhr.status}`));
|
reject(new Error(`HTTP Error: ${xhr.status}`));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.onerror = () => reject(new Error("Network Error"));
|
xhr.onerror = () => {
|
||||||
|
this.controllers.delete(id);
|
||||||
|
// 如果不是因为中止导致的错误
|
||||||
|
if (!controller.signal.aborted) {
|
||||||
|
reject(new Error("Network Error"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onabort = () => {
|
||||||
|
this.controllers.delete(id);
|
||||||
|
if (controller.signal.aborted) {
|
||||||
|
reject(new DOMException('Upload aborted', 'AbortError'));
|
||||||
|
}
|
||||||
|
};
|
||||||
xhr.open(method, uploadUrl);
|
xhr.open(method, uploadUrl);
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
// PUT 模式:直接上传文件
|
// PUT 模式:直接上传文件
|
||||||
if (file instanceof File) {
|
if (file instanceof File) {
|
||||||
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
||||||
}
|
}
|
||||||
|
else if (file instanceof Blob) {
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
||||||
|
}
|
||||||
xhr.send(file);
|
xhr.send(file);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -55,32 +109,85 @@ class Upload {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 无进度监听模式(直接 fetch)
|
// 无进度监听模式(直接 fetch)
|
||||||
if (isPut) {
|
try {
|
||||||
// S3 预签名上传
|
let result;
|
||||||
const headers = {};
|
if (isPut) {
|
||||||
if (file instanceof File) {
|
// S3 预签名上传
|
||||||
headers["Content-Type"] = file.type || "application/octet-stream";
|
const headers = {};
|
||||||
|
if (file instanceof File) {
|
||||||
|
headers["Content-Type"] = file.type || "application/octet-stream";
|
||||||
|
}
|
||||||
|
else if (file instanceof Blob) {
|
||||||
|
headers["Content-Type"] = "application/octet-stream";
|
||||||
|
}
|
||||||
|
result = await fetch(uploadUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
headers,
|
||||||
|
body: file,
|
||||||
|
signal: controller.signal, // 添加中止信号
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const result = await fetch(uploadUrl, {
|
else {
|
||||||
method: "PUT",
|
// 表单上传
|
||||||
headers,
|
const formData2 = new FormData();
|
||||||
body: file,
|
for (const key of Object.keys(formData)) {
|
||||||
});
|
formData2.append(key, formData[key]);
|
||||||
|
}
|
||||||
|
formData2.append(name || "file", file);
|
||||||
|
result = await fetch(uploadUrl, {
|
||||||
|
method,
|
||||||
|
body: formData2,
|
||||||
|
signal: controller.signal, // 添加中止信号
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.controllers.delete(id); // 成功后清理控制器
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
else {
|
catch (error) {
|
||||||
// 表单上传
|
this.controllers.delete(id); // 失败后清理控制器
|
||||||
const formData2 = new FormData();
|
// 人为中断返回204 使general-business处理成功
|
||||||
for (const key of Object.keys(formData)) {
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||||
formData2.append(key, formData[key]);
|
throw new DOMException('Upload aborted', 'AbortError');
|
||||||
}
|
}
|
||||||
formData2.append(name || "file", file);
|
throw error;
|
||||||
const result = await fetch(uploadUrl, {
|
|
||||||
method,
|
|
||||||
body: formData2,
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 中断特定上传
|
||||||
|
abortUpload(uploadId) {
|
||||||
|
const controller = this.controllers.get(uploadId);
|
||||||
|
if (controller) {
|
||||||
|
controller.abort();
|
||||||
|
this.controllers.delete(uploadId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 中断所有上传
|
||||||
|
abortAllUploads() {
|
||||||
|
this.controllers.forEach((controller, id) => {
|
||||||
|
controller.abort();
|
||||||
|
});
|
||||||
|
this.controllers.clear();
|
||||||
|
}
|
||||||
|
// 获取上传状态
|
||||||
|
getUploadStatus(uploadId) {
|
||||||
|
const controller = this.controllers.get(uploadId);
|
||||||
|
if (!controller)
|
||||||
|
return 'not-found';
|
||||||
|
if (controller.signal.aborted)
|
||||||
|
return 'aborted';
|
||||||
|
return 'uploading';
|
||||||
|
}
|
||||||
|
// 生成唯一的上传ID
|
||||||
|
generateUploadId(file, uploadUrl) {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const random = Math.random().toString(36).substring(2, 9);
|
||||||
|
const fileInfo = file instanceof File ? `${file.name}-${file.size}` : file instanceof Blob ? `blob-${file.size}` : file;
|
||||||
|
return `${uploadUrl}-${fileInfo}-${timestamp}-${random}`;
|
||||||
|
}
|
||||||
|
// 获取所有进行中的上传任务
|
||||||
|
getActiveUploads() {
|
||||||
|
return Array.from(this.controllers.keys());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exports.Upload = Upload;
|
exports.Upload = Upload;
|
||||||
|
|
|
||||||
10
package.json
10
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "oak-frontend-base",
|
"name": "oak-frontend-base",
|
||||||
"version": "5.3.38",
|
"version": "5.3.46",
|
||||||
"description": "oak框架中前端与业务逻辑无关的平台部分",
|
"description": "oak框架中前端与业务逻辑无关的平台部分",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "XuChang"
|
"name": "XuChang"
|
||||||
|
|
@ -22,9 +22,9 @@
|
||||||
"i18n-js": "^4.3.0",
|
"i18n-js": "^4.3.0",
|
||||||
"node-schedule": "^2.1.1",
|
"node-schedule": "^2.1.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"oak-common-aspect": "^3.0.5",
|
"oak-common-aspect": "file:../oak-common-aspect",
|
||||||
"oak-domain": "^5.1.28",
|
"oak-domain": "file:../oak-domain",
|
||||||
"oak-memory-tree-store": "^3.3.13",
|
"oak-memory-tree-store": "file:../oak-memory-tree-store",
|
||||||
"ol": "^7.3.0",
|
"ol": "^7.3.0",
|
||||||
"rc-pagination": "^4.3.0",
|
"rc-pagination": "^4.3.0",
|
||||||
"react-activation": "^0.12.4",
|
"react-activation": "^0.12.4",
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
"@types/node": "^20.7.0",
|
"@types/node": "^20.7.0",
|
||||||
"@types/node-schedule": "^2.1.0",
|
"@types/node-schedule": "^2.1.0",
|
||||||
"@types/nprogress": "^0.2.3",
|
"@types/nprogress": "^0.2.3",
|
||||||
"@types/react": "^18.3.26",
|
"@types/react": "^18.3.27",
|
||||||
"@types/react-dom": "^18.2.14",
|
"@types/react-dom": "^18.2.14",
|
||||||
"@types/react-native": "^0.72.8",
|
"@types/react-native": "^0.72.8",
|
||||||
"@types/uuid": "^9.0.6",
|
"@types/uuid": "^9.0.6",
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export default function Render(
|
||||||
showTitle,
|
showTitle,
|
||||||
} = props.data;
|
} = props.data;
|
||||||
const { t, setPageSize, setCurrentPage } = props.methods;
|
const { t, setPageSize, setCurrentPage } = props.methods;
|
||||||
const { pageSize, total, currentPage, more, count, getTotal } = oakPagination || {};
|
const { pageSize = 0, total = 0, currentPage = 1, more, count, getTotal } = oakPagination || {};
|
||||||
|
|
||||||
const paginationRef = useRef<HTMLUListElement>(null);
|
const paginationRef = useRef<HTMLUListElement>(null);
|
||||||
const [internalInputVal, setInternalInputVal] = useState(currentPage);
|
const [internalInputVal, setInternalInputVal] = useState(currentPage);
|
||||||
|
|
@ -71,7 +71,7 @@ export default function Render(
|
||||||
if (!oakPagination) {
|
if (!oakPagination) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (total === 0) {
|
if (!total) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,13 +108,12 @@ export class Navigator extends CommonNavigator {
|
||||||
wx.navigateTo({
|
wx.navigateTo({
|
||||||
url: url,
|
url: url,
|
||||||
success: () => {
|
success: () => {
|
||||||
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.publish();
|
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,12 +132,12 @@ export class Navigator extends CommonNavigator {
|
||||||
wx.redirectTo({
|
wx.redirectTo({
|
||||||
url: url,
|
url: url,
|
||||||
success: () => {
|
success: () => {
|
||||||
resolve(undefined);
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,12 +155,12 @@ export class Navigator extends CommonNavigator {
|
||||||
wx.switchTab({
|
wx.switchTab({
|
||||||
url: url,
|
url: url,
|
||||||
success: () => {
|
success: () => {
|
||||||
resolve(undefined);
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,12 +172,12 @@ export class Navigator extends CommonNavigator {
|
||||||
wx.navigateBack({
|
wx.navigateBack({
|
||||||
delta: delta || 1,
|
delta: delta || 1,
|
||||||
success: () => {
|
success: () => {
|
||||||
resolve(undefined);
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
|
resolve(undefined);
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err),
|
fail: (err) => reject(err),
|
||||||
});
|
});
|
||||||
this.leave();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,20 +192,14 @@ export class Navigator extends CommonNavigator {
|
||||||
state?: Record<string, any>,
|
state?: Record<string, any>,
|
||||||
disableNamespace?: boolean
|
disableNamespace?: boolean
|
||||||
) {
|
) {
|
||||||
if (!this.enter()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pages = getCurrentPages();
|
const pages = getCurrentPages();
|
||||||
if (pages.length > 1) {
|
if (pages.length > 1) {
|
||||||
this.leave();
|
|
||||||
return this.navigateBack();
|
return this.navigateBack();
|
||||||
}
|
}
|
||||||
const isTabBar = options?.isTabBar;
|
const isTabBar = options?.isTabBar;
|
||||||
if (isTabBar) {
|
if (isTabBar) {
|
||||||
this.leave();
|
|
||||||
return this.switchTab(options, state, disableNamespace);
|
return this.switchTab(options, state, disableNamespace);
|
||||||
}
|
}
|
||||||
this.leave();
|
|
||||||
return this.redirectTo(options, state, disableNamespace);
|
return this.redirectTo(options, state, disableNamespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,8 @@ export class Navigator extends CommonNavigator {
|
||||||
);
|
);
|
||||||
const replaceAction = StackActions.replace(url, props);
|
const replaceAction = StackActions.replace(url, props);
|
||||||
this.history.dispatch(replaceAction);
|
this.history.dispatch(replaceAction);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
async switchTab<
|
async switchTab<
|
||||||
|
|
@ -131,8 +131,8 @@ export class Navigator extends CommonNavigator {
|
||||||
);
|
);
|
||||||
const jumpToAction = TabActions.jumpTo(url, props);
|
const jumpToAction = TabActions.jumpTo(url, props);
|
||||||
this.history.dispatch(jumpToAction);
|
this.history.dispatch(jumpToAction);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
async navigateBack(delta?: number) {
|
async navigateBack(delta?: number) {
|
||||||
|
|
@ -143,9 +143,12 @@ export class Navigator extends CommonNavigator {
|
||||||
if (canGoBack) {
|
if (canGoBack) {
|
||||||
const popAction = StackActions.pop(delta || 1);
|
const popAction = StackActions.pop(delta || 1);
|
||||||
this.history.dispatch(popAction);
|
this.history.dispatch(popAction);
|
||||||
|
this.leave();
|
||||||
this.publish();
|
this.publish();
|
||||||
}
|
}
|
||||||
this.leave();
|
else {
|
||||||
|
this.leave();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateBackOrRedirectTo<
|
navigateBackOrRedirectTo<
|
||||||
|
|
@ -165,13 +168,13 @@ export class Navigator extends CommonNavigator {
|
||||||
const canGoBack = this.history.canGoBack();
|
const canGoBack = this.history.canGoBack();
|
||||||
if (canGoBack) {
|
if (canGoBack) {
|
||||||
this.navigateBack();
|
this.navigateBack();
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 回最顶层
|
// 回最顶层
|
||||||
this.history.dispatch(StackActions.popToTop());
|
this.history.dispatch(StackActions.popToTop());
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,8 @@ export class Navigator extends CommonNavigator {
|
||||||
disableNamespace
|
disableNamespace
|
||||||
);
|
);
|
||||||
this.history.push(url, props);
|
this.history.push(url, props);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
async redirectTo<
|
async redirectTo<
|
||||||
|
|
@ -103,8 +103,8 @@ export class Navigator extends CommonNavigator {
|
||||||
disableNamespace
|
disableNamespace
|
||||||
);
|
);
|
||||||
this.history.replace(url, props);
|
this.history.replace(url, props);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
async switchTab<
|
async switchTab<
|
||||||
|
|
@ -130,8 +130,8 @@ export class Navigator extends CommonNavigator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.history.go(delta ? 0 - delta : -1);
|
this.history.go(delta ? 0 - delta : -1);
|
||||||
this.publish();
|
|
||||||
this.leave();
|
this.leave();
|
||||||
|
this.publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateBackOrRedirectTo<
|
navigateBackOrRedirectTo<
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { Cache } from './cache';
|
||||||
import { Pagination } from '../types/Pagination';
|
import { Pagination } from '../types/Pagination';
|
||||||
import { Feature } from '../types/Feature';
|
import { Feature } from '../types/Feature';
|
||||||
import { ActionDef, CreateDataDef } from '../types/Page';
|
import { ActionDef, CreateDataDef } from '../types/Page';
|
||||||
import { generateNewId } from 'oak-domain/lib/utils/uuid';
|
import { generateNewId, generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||||
|
|
||||||
export const MODI_NEXT_PATH_SUFFIX = ':next';
|
export const MODI_NEXT_PATH_SUFFIX = ':next';
|
||||||
const START_LSN = 100;
|
const START_LSN = 100;
|
||||||
|
|
@ -664,14 +664,14 @@ class ListNode<
|
||||||
/** 检查原本就在的,现在还在不在,
|
/** 检查原本就在的,现在还在不在,
|
||||||
* 以及原本不在的,现在是不是满足条件了
|
* 以及原本不在的,现在是不是满足条件了
|
||||||
* (后一种情况也可能原本就满足但不在当前的ids中,这时是判断不出来的,total+1可能不对,但是应该是较少的case)*/
|
* (后一种情况也可能原本就满足但不在当前的ids中,这时是判断不出来的,total+1可能不对,但是应该是较少的case)*/
|
||||||
const filter = this.constructFilters(true, true, true);
|
const filters = this.constructFilters(true, true, true);
|
||||||
if (intersected.length) {
|
if (intersected.length) {
|
||||||
const rows = this.cache.get(this.entity, {
|
const rows = this.cache.get(this.entity, {
|
||||||
data: {
|
data: {
|
||||||
id: 1,
|
id: 1,
|
||||||
},
|
},
|
||||||
filter: combineFilters(this.entity, this.schema, [
|
filter: combineFilters(this.entity, this.schema, [
|
||||||
...(filter || []),
|
...(filters || []),
|
||||||
{
|
{
|
||||||
id: {
|
id: {
|
||||||
$in: intersected,
|
$in: intersected,
|
||||||
|
|
@ -691,13 +691,9 @@ class ListNode<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (diffed.length) {
|
if (diffed.length) {
|
||||||
|
const filter = filters && combineFilters(e, this.schema, filters);
|
||||||
diffed.forEach(
|
diffed.forEach(
|
||||||
(ele) => {
|
(ele) => tryAddRowToList(ele, filter)
|
||||||
this.sr[ele] = {};
|
|
||||||
if (this.pagination.total) {
|
|
||||||
this.pagination.total++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1552,7 +1548,7 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
|
||||||
setId(id: string) {
|
setId(id: string) {
|
||||||
if (id !== this.id) {
|
if (id !== this.id) {
|
||||||
const operations = this.ulManager.makeOperations();
|
const operations = this.ulManager.makeOperations();
|
||||||
assert(operations.length <= 1);
|
assert(operations.length <= 1, 'singleNode在setId时出现数据不一致');
|
||||||
const [operation] = operations;
|
const [operation] = operations;
|
||||||
if (operation?.action === 'create') {
|
if (operation?.action === 'create') {
|
||||||
if (operation.data.id === id) {
|
if (operation.data.id === id) {
|
||||||
|
|
@ -1731,7 +1727,7 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
|
||||||
|
|
||||||
setDirty(): void {
|
setDirty(): void {
|
||||||
const id = this.getId();
|
const id = this.getId();
|
||||||
assert(id);
|
assert(id, "不能对没有id的singleNode设置dirty");
|
||||||
this.ulManager.push(this.ulManager.maxLsn, {
|
this.ulManager.push(this.ulManager.maxLsn, {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
data: {},
|
data: {},
|
||||||
|
|
@ -1763,7 +1759,7 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
|
||||||
const childOperations = child!.composeOperations(paths?.length ? paths : undefined);
|
const childOperations = child!.composeOperations(paths?.length ? paths : undefined);
|
||||||
if (childOperations) {
|
if (childOperations) {
|
||||||
if (child instanceof SingleNode) {
|
if (child instanceof SingleNode) {
|
||||||
assert(childOperations.length === 1);
|
assert(childOperations.length === 1, 'singleNode在composeOperations时出现数据不一致');
|
||||||
this.ulManager.push(lsnMax + 100, {
|
this.ulManager.push(lsnMax + 100, {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -1775,7 +1771,7 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
|
||||||
} as any);
|
} as any);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assert(child instanceof ListNode);
|
assert(child instanceof ListNode, 'child必须是ListNode');
|
||||||
this.ulManager.push(lsnMax + 100, {
|
this.ulManager.push(lsnMax + 100, {
|
||||||
action: 'update',
|
action: 'update',
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -1872,8 +1868,8 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
|
||||||
const rel = this.judgeRelation(k2);
|
const rel = this.judgeRelation(k2);
|
||||||
if (rel === 2) {
|
if (rel === 2) {
|
||||||
if (value?.entityId) {
|
if (value?.entityId) {
|
||||||
assert(child instanceof SingleNode);
|
assert(child instanceof SingleNode, 'child必须是singleNode');
|
||||||
assert(value.entity === child.getEntity());
|
assert(value.entity === child.getEntity(), 'singleNode的entity必须一致');
|
||||||
child.saveRefreshResult({
|
child.saveRefreshResult({
|
||||||
[value.entityId!]: this.sr[k2] || {},
|
[value.entityId!]: this.sr[k2] || {},
|
||||||
});
|
});
|
||||||
|
|
@ -1969,7 +1965,7 @@ class SingleNode<ED extends EntityDict & BaseEntityDict,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFilter(ignoreNew?: true, onlyHot?: true): ED[T]['Filter'] | undefined {
|
getFilter(ignoreNew?: true, onlyHot?: true): ED[T]['Filter'] | undefined {
|
||||||
// 如果是新建,等于没有filter
|
// 如果是新建,等于没有filter
|
||||||
const [operation] = this.ulManager.makeOperations();
|
const [operation] = this.ulManager.makeOperations();
|
||||||
if (operation?.action === 'create') {
|
if (operation?.action === 'create') {
|
||||||
|
|
@ -2441,6 +2437,7 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
|
||||||
assert(!parentNode || parentNode instanceof VirtualNode);
|
assert(!parentNode || parentNode instanceof VirtualNode);
|
||||||
node = new VirtualNode(fullPath, path, parentNode, stale);
|
node = new VirtualNode(fullPath, path, parentNode, stale);
|
||||||
}
|
}
|
||||||
|
// 任何临时的修改都需要回滚,上面缓存了一些update操作
|
||||||
rollback();
|
rollback();
|
||||||
if (!parentNode) {
|
if (!parentNode) {
|
||||||
assert(!parent && !this.root[path]);
|
assert(!parent && !this.root[path]);
|
||||||
|
|
@ -2974,7 +2971,7 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
|
||||||
|
|
||||||
node && node.setExecuting(true);
|
node && node.setExecuting(true);
|
||||||
|
|
||||||
let pollute = false;
|
// let pollute = false;
|
||||||
try {
|
try {
|
||||||
let operations = path && this.getOperations(path) || [];
|
let operations = path && this.getOperations(path) || [];
|
||||||
if (opers) {
|
if (opers) {
|
||||||
|
|
@ -2994,15 +2991,30 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
|
||||||
else if (node) {
|
else if (node) {
|
||||||
// 老的写法,直接对一个非脏的结点execute某个action,也可以支持
|
// 老的写法,直接对一个非脏的结点execute某个action,也可以支持
|
||||||
assert(node instanceof SingleNode);
|
assert(node instanceof SingleNode);
|
||||||
node.update(this.logSerailNumber, {}, action);
|
// node.update(this.logSerailNumber, {}, action);
|
||||||
pollute = true;
|
// pollute = true;
|
||||||
operations = node.composeOperations() || [];
|
// operations = node.composeOperations() || [];
|
||||||
assert(operations.length === 1);
|
// assert(operations.length === 1);
|
||||||
const [operation1] = operations;
|
// const [operation1] = operations;
|
||||||
if (action !== operation1.operation.action) {
|
// if (action !== operation1.operation.action) {
|
||||||
assert(operation1.operation.action === 'update'); // 如果execute时传action,前面update动作应该只可能是update
|
// assert(operation1.operation.action === 'update'); // 如果execute时传action,前面update动作应该只可能是update
|
||||||
operation1.operation.action = action;
|
// operation1.operation.action = action;
|
||||||
}
|
// }
|
||||||
|
/**
|
||||||
|
* 上述写法会触发publish行为,如:一个close动作会导致页面像下面这样渲染:
|
||||||
|
* opened --> closed(上面的node.update) --> opened(执行成功回来setExecuting时) --> closed(执行成功回来再sync cache后)
|
||||||
|
*/
|
||||||
|
operations.push({
|
||||||
|
entity: node.getEntity(),
|
||||||
|
operation: {
|
||||||
|
id: await generateNewIdAsync(),
|
||||||
|
action,
|
||||||
|
data: {
|
||||||
|
$$updateAt$$: Date.now(),
|
||||||
|
},
|
||||||
|
filter: node.getFilter(true),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (operations.length > 0) {
|
if (operations.length > 0) {
|
||||||
|
|
@ -3042,9 +3054,9 @@ export class RunningTree<ED extends EntityDict & BaseEntityDict> extends Feature
|
||||||
return { message: 'No Operation' };
|
return { message: 'No Operation' };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
node && node.setExecuting(false);
|
node && node.setExecuting(false);
|
||||||
if (pollute) {
|
/* if (pollute) {
|
||||||
path && this.clean(path);
|
path && this.clean(path);
|
||||||
}
|
} */
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,13 +127,13 @@ export class SubScriber<ED extends EntityDict & BaseEntityDict> extends Feature
|
||||||
socket.on(
|
socket.on(
|
||||||
'data',
|
'data',
|
||||||
(opRecords: OpRecord<ED>[], event: string) => {
|
(opRecords: OpRecord<ED>[], event: string) => {
|
||||||
|
this.cache.sync(opRecords);
|
||||||
const registered = this.eventMap[event];
|
const registered = this.eventMap[event];
|
||||||
if (registered) {
|
if (registered) {
|
||||||
registered.callbacks.forEach(
|
registered.callbacks.forEach(
|
||||||
(ele) => ele(event, opRecords)
|
(ele) => ele(event, opRecords)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.cache.sync(opRecords);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -773,6 +773,13 @@ function translatePropertiesToPropertyDefinitions(properties?: DataOption) {
|
||||||
if (properties) {
|
if (properties) {
|
||||||
Object.keys(properties).forEach(
|
Object.keys(properties).forEach(
|
||||||
(prop) => {
|
(prop) => {
|
||||||
|
if (properties[prop] === null) {
|
||||||
|
definitions[prop] = {
|
||||||
|
type: null,
|
||||||
|
value: null,
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (typeof properties[prop]) {
|
switch (typeof properties[prop]) {
|
||||||
case 'string': {
|
case 'string': {
|
||||||
if (properties[prop]) {
|
if (properties[prop]) {
|
||||||
|
|
@ -851,7 +858,7 @@ export function createComponent<
|
||||||
T extends keyof ED,
|
T extends keyof ED,
|
||||||
Cxt extends AsyncContext<ED>,
|
Cxt extends AsyncContext<ED>,
|
||||||
FrontCxt extends SyncContext<ED>,
|
FrontCxt extends SyncContext<ED>,
|
||||||
AD extends Record<string, Aspect<ED, AsyncContext<ED>>>,
|
AD extends Record<string, Aspect<ED, Cxt>>,
|
||||||
FD extends Record<string, Feature>,
|
FD extends Record<string, Feature>,
|
||||||
FormedData extends Record<string, any>,
|
FormedData extends Record<string, any>,
|
||||||
TData extends DataOption = {},
|
TData extends DataOption = {},
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export function createComponent<
|
||||||
T extends keyof ED,
|
T extends keyof ED,
|
||||||
Cxt extends AsyncContext<ED>,
|
Cxt extends AsyncContext<ED>,
|
||||||
FrontCxt extends SyncContext<ED>,
|
FrontCxt extends SyncContext<ED>,
|
||||||
AD extends Record<string, Aspect<ED, AsyncContext<ED>>>,
|
AD extends Record<string, Aspect<ED, Cxt>>,
|
||||||
FD extends Record<string, Feature>,
|
FD extends Record<string, Feature>,
|
||||||
FormedData extends Record<string, any>,
|
FormedData extends Record<string, any>,
|
||||||
TData extends Record<string, any> = {},
|
TData extends Record<string, any> = {},
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ abstract class OakComponentBase<
|
||||||
T extends keyof ED,
|
T extends keyof ED,
|
||||||
Cxt extends AsyncContext<ED>,
|
Cxt extends AsyncContext<ED>,
|
||||||
FrontCxt extends SyncContext<ED>,
|
FrontCxt extends SyncContext<ED>,
|
||||||
AD extends Record<string, Aspect<ED, AsyncContext<ED>>>,
|
AD extends Record<string, Aspect<ED, Cxt>>,
|
||||||
FD extends Record<string, Feature>,
|
FD extends Record<string, Feature>,
|
||||||
FormedData extends Record<string, any>,
|
FormedData extends Record<string, any>,
|
||||||
IsList extends boolean,
|
IsList extends boolean,
|
||||||
|
|
@ -650,7 +650,7 @@ export function createComponent<
|
||||||
T extends keyof ED,
|
T extends keyof ED,
|
||||||
Cxt extends AsyncContext<ED>,
|
Cxt extends AsyncContext<ED>,
|
||||||
FrontCxt extends SyncContext<ED>,
|
FrontCxt extends SyncContext<ED>,
|
||||||
AD extends Record<string, Aspect<ED, AsyncContext<ED>>>,
|
AD extends Record<string, Aspect<ED, Cxt>>,
|
||||||
FD extends Record<string, Feature>,
|
FD extends Record<string, Feature>,
|
||||||
FormedData extends Record<string, any>,
|
FormedData extends Record<string, any>,
|
||||||
TData extends Record<string, any> = {},
|
TData extends Record<string, any> = {},
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export function createComponent<
|
||||||
T extends keyof ED,
|
T extends keyof ED,
|
||||||
Cxt extends AsyncContext<ED>,
|
Cxt extends AsyncContext<ED>,
|
||||||
FrontCxt extends SyncContext<ED>,
|
FrontCxt extends SyncContext<ED>,
|
||||||
AD extends Record<string, Aspect<ED, AsyncContext<ED>>>,
|
AD extends Record<string, Aspect<ED, Cxt>>,
|
||||||
FD extends Record<string, Feature>,
|
FD extends Record<string, Feature>,
|
||||||
FormedData extends Record<string, any>,
|
FormedData extends Record<string, any>,
|
||||||
TData extends Record<string, any> = {},
|
TData extends Record<string, any> = {},
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ export type OakComponentOption<
|
||||||
T extends keyof ED,
|
T extends keyof ED,
|
||||||
Cxt extends AsyncContext<ED>,
|
Cxt extends AsyncContext<ED>,
|
||||||
FrontCxt extends SyncContext<ED>,
|
FrontCxt extends SyncContext<ED>,
|
||||||
AD extends Record<string, Aspect<ED, AsyncContext<ED>>>,
|
AD extends Record<string, Aspect<ED, Cxt>>,
|
||||||
FD extends Record<string, Feature>,
|
FD extends Record<string, Feature>,
|
||||||
FormedData extends Record<string, any>,
|
FormedData extends Record<string, any>,
|
||||||
TData extends Record<string, any>,
|
TData extends Record<string, any>,
|
||||||
|
|
@ -599,7 +599,7 @@ export type MakeOakComponent<
|
||||||
ED extends EntityDict & BaseEntityDict,
|
ED extends EntityDict & BaseEntityDict,
|
||||||
Cxt extends AsyncContext<ED>,
|
Cxt extends AsyncContext<ED>,
|
||||||
FrontCxt extends SyncContext<ED>,
|
FrontCxt extends SyncContext<ED>,
|
||||||
AD extends Record<string, Aspect<ED, AsyncContext<ED>>>,
|
AD extends Record<string, Aspect<ED, Cxt>>,
|
||||||
FD extends Record<string, Feature>
|
FD extends Record<string, Feature>
|
||||||
> = <
|
> = <
|
||||||
IsList extends boolean,
|
IsList extends boolean,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
type UploadFileFn = (options: {
|
||||||
|
// 需要上传的内容
|
||||||
|
file: string | File | Blob;
|
||||||
|
// 表单字段名
|
||||||
|
name: string;
|
||||||
|
// 上传地址
|
||||||
|
uploadUrl: string;
|
||||||
|
// 额外的表单数据
|
||||||
|
formData: Record<string, any>;
|
||||||
|
// 是否自动通知上传结果
|
||||||
|
autoInform?: boolean;
|
||||||
|
// 进度回调函数
|
||||||
|
getPercent?: Function;
|
||||||
|
// 上传任务ID,用于中断特定上传
|
||||||
|
uploadId?: string;
|
||||||
|
// HTTP方法
|
||||||
|
method?: "POST" | "PUT" | "PATCH";
|
||||||
|
// 是否为文件路径(主要用于小程序,在PUT模式下需要)
|
||||||
|
isFilePath?: boolean;
|
||||||
|
}) => Promise<{
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
statusCode?: number;
|
||||||
|
headers: {
|
||||||
|
get(name: string): string | null;
|
||||||
|
};
|
||||||
|
json(): Promise<any>;
|
||||||
|
text(): Promise<string>;
|
||||||
|
errMsg?: string;
|
||||||
|
data?: any;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export interface UploadInterface {
|
||||||
|
uploadFile: UploadFileFn;
|
||||||
|
abortUpload(uploadId: string): boolean;
|
||||||
|
abortAllUploads(): void;
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
|
||||||
|
getActiveUploads(): string[];
|
||||||
|
}
|
||||||
|
|
@ -1,50 +1,67 @@
|
||||||
|
import { UploadInterface } from '../types/Upload';
|
||||||
import { promisify } from './promisify';
|
import { promisify } from './promisify';
|
||||||
|
|
||||||
export class Upload {
|
export class Upload implements UploadInterface {
|
||||||
|
|
||||||
|
abortUpload(uploadId: string): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
abortAllUploads(): void {
|
||||||
|
}
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found' {
|
||||||
|
return 'not-found';
|
||||||
|
}
|
||||||
|
getActiveUploads(): string[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
async uploadFile(
|
async uploadFile(
|
||||||
file: string | File,
|
options: {
|
||||||
name: string,
|
file: string | File | Blob,
|
||||||
uploadUrl: string,
|
name: string,
|
||||||
formData: Record<string, any>,
|
uploadUrl: string,
|
||||||
autoInform?: boolean,
|
formData: Record<string, any>,
|
||||||
getPercent?: Function,
|
autoInform?: boolean,
|
||||||
method: "POST" | "PUT" | "PATCH" = "POST"
|
getPercent?: Function,
|
||||||
|
uploadId?: string, // 新增:上传任务ID,用于中断特定上传
|
||||||
|
method?: "POST" | "PUT" | "PATCH"
|
||||||
|
isFilePath?: boolean
|
||||||
|
}
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
const { file, name, uploadUrl, formData, isFilePath, method = "POST" } = options;
|
||||||
const isPut = method === "PUT";
|
const isPut = method === "PUT";
|
||||||
|
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
return new Promise((resolve, reject) => {
|
if (isFilePath) {
|
||||||
const fs = wx.getFileSystemManager();
|
return new Promise((resolve, reject) => {
|
||||||
fs.readFile({
|
const fs = wx.getFileSystemManager();
|
||||||
filePath: file as string,
|
fs.readFile({
|
||||||
success: (fileRes) => {
|
filePath: file as string,
|
||||||
// 使用 PUT 方法上传
|
encoding: 'binary',
|
||||||
wx.request({
|
success: res => {
|
||||||
url: uploadUrl,
|
resolve(global.fetch(uploadUrl, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
data: fileRes.data, // ArrayBuffer 格式
|
headers: {
|
||||||
header: {
|
'Content-Type': 'application/octet-stream',
|
||||||
'Content-Type': 'image/jpeg', // 根据实际文件类型设置
|
},
|
||||||
},
|
body: res.data,
|
||||||
success: (uploadRes) => {
|
}));
|
||||||
if (uploadRes.statusCode === 200) {
|
}
|
||||||
resolve(uploadRes);
|
,
|
||||||
} else {
|
fail: err => {
|
||||||
reject(new Error(`HTTP Error: ${uploadRes.statusCode}`));
|
reject(err);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
fail: (err) => {
|
|
||||||
console.error('上传失败', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('读取文件失败', err);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return global.fetch(uploadUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
},
|
||||||
|
body: file as any,
|
||||||
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,34 @@
|
||||||
|
import { UploadInterface } from "../types/Upload";
|
||||||
|
|
||||||
|
|
||||||
export class Upload {
|
export class Upload implements UploadInterface {
|
||||||
|
|
||||||
|
abortUpload(uploadId: string): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
abortAllUploads(): void {
|
||||||
|
}
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found' {
|
||||||
|
return 'not-found';
|
||||||
|
}
|
||||||
|
getActiveUploads(): string[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
async uploadFile(
|
async uploadFile(
|
||||||
file: File | string,
|
options: {
|
||||||
name: string,
|
file: File | string | Blob,
|
||||||
uploadUrl: string,
|
name: string,
|
||||||
formData: Record<string, any>,
|
uploadUrl: string,
|
||||||
autoInform?: boolean,
|
formData: Record<string, any>,
|
||||||
getPercent?: Function,
|
autoInform?: boolean,
|
||||||
method: "POST" | "PUT" | "PATCH" = "POST"
|
getPercent?: Function,
|
||||||
|
uploadId?: string, // 新增:上传任务ID,用于中断特定上传
|
||||||
|
method?: "POST" | "PUT" | "PATCH"
|
||||||
|
}
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
const { file, name, uploadUrl, formData, method = "POST" } = options;
|
||||||
|
|
||||||
const isPut = method === "PUT";
|
const isPut = method === "PUT";
|
||||||
|
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,57 @@
|
||||||
|
import { UploadInterface } from "../types/Upload";
|
||||||
|
|
||||||
export class Upload {
|
|
||||||
|
export class Upload implements UploadInterface {
|
||||||
async uploadFile(
|
async uploadFile(
|
||||||
file: string | File,
|
options: {
|
||||||
name: string,
|
file: string | File | Blob,
|
||||||
uploadUrl: string,
|
name: string,
|
||||||
formData: Record<string, any>,
|
uploadUrl: string,
|
||||||
autoInform?: boolean
|
formData: Record<string, any>,
|
||||||
|
autoInform?: boolean,
|
||||||
|
getPercent?: Function,
|
||||||
|
uploadId?: string, // 新增:上传任务ID,用于中断特定上传
|
||||||
|
method?: "POST" | "PUT" | "PATCH",
|
||||||
|
isFilePath?: boolean,
|
||||||
|
}
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
console.warn('server不会调用此函数')
|
console.warn('server不会调用此函数')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private controllers: Map<string, AbortController> = new Map();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.uploadFile = this.uploadFile.bind(this);
|
||||||
|
this.abortUpload = this.abortUpload.bind(this);
|
||||||
|
this.abortAllUploads = this.abortAllUploads.bind(this);
|
||||||
|
this.getUploadStatus = this.getUploadStatus.bind(this);
|
||||||
|
this.getActiveUploads = this.getActiveUploads.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中断特定上传
|
||||||
|
abortUpload(uploadId: string): boolean {
|
||||||
|
console.warn('server不会调用此函数')
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中断所有上传
|
||||||
|
abortAllUploads(): void {
|
||||||
|
console.warn('server不会调用此函数')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取上传状态
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found' {
|
||||||
|
console.warn('server不会调用此函数')
|
||||||
|
return 'uploading';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成唯一的上传ID
|
||||||
|
private generateUploadId(file: File | string, uploadUrl: string): string {
|
||||||
|
return `server不会调用此函数`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有进行中的上传任务
|
||||||
|
getActiveUploads(): string[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +1,42 @@
|
||||||
|
import { OakUserException } from "oak-domain/lib/types";
|
||||||
|
import { UploadInterface } from "../types/Upload";
|
||||||
|
|
||||||
|
export class Upload implements UploadInterface {
|
||||||
|
private controllers: Map<string, AbortController> = new Map();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.uploadFile = this.uploadFile.bind(this);
|
||||||
|
this.abortUpload = this.abortUpload.bind(this);
|
||||||
|
this.abortAllUploads = this.abortAllUploads.bind(this);
|
||||||
|
this.getUploadStatus = this.getUploadStatus.bind(this);
|
||||||
|
this.getActiveUploads = this.getActiveUploads.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
export class Upload {
|
|
||||||
async uploadFile(
|
async uploadFile(
|
||||||
file: File | string,
|
options: {
|
||||||
name: string,
|
file: File | string | Blob,
|
||||||
uploadUrl: string,
|
name: string,
|
||||||
formData: Record<string, any>,
|
uploadUrl: string,
|
||||||
autoInform?: boolean,
|
formData: Record<string, any>,
|
||||||
getPercent?: Function,
|
autoInform?: boolean,
|
||||||
method: "POST" | "PUT" | "PATCH" = "POST"
|
getPercent?: Function,
|
||||||
|
uploadId?: string, // 新增:上传任务ID,用于中断特定上传
|
||||||
|
method?: "POST" | "PUT" | "PATCH"
|
||||||
|
}
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
const { file, name, uploadUrl, formData, getPercent, uploadId, method = "POST" } = options;
|
||||||
|
|
||||||
const isPut = method === "PUT";
|
const isPut = method === "PUT";
|
||||||
|
const id = uploadId || this.generateUploadId(file, uploadUrl);
|
||||||
|
|
||||||
|
// 如果已有相同ID的上传在进行,先中断它
|
||||||
|
if (this.controllers.has(id)) {
|
||||||
|
this.abortUpload(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的 AbortController
|
||||||
|
const controller = new AbortController();
|
||||||
|
this.controllers.set(id, controller);
|
||||||
|
|
||||||
// 进度监听模式
|
// 进度监听模式
|
||||||
if (getPercent) {
|
if (getPercent) {
|
||||||
|
|
@ -18,6 +44,12 @@ export class Upload {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
let percent = 0;
|
let percent = 0;
|
||||||
|
|
||||||
|
// 监听中止信号
|
||||||
|
controller.signal.addEventListener('abort', () => {
|
||||||
|
xhr.abort();
|
||||||
|
reject(new DOMException('Upload aborted', 'AbortError'));
|
||||||
|
});
|
||||||
|
|
||||||
xhr.upload.addEventListener("progress", (event) => {
|
xhr.upload.addEventListener("progress", (event) => {
|
||||||
if (event.lengthComputable) {
|
if (event.lengthComputable) {
|
||||||
percent = Math.round((event.loaded / event.total) * 100);
|
percent = Math.round((event.loaded / event.total) * 100);
|
||||||
|
|
@ -26,15 +58,35 @@ export class Upload {
|
||||||
});
|
});
|
||||||
|
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
|
this.controllers.delete(id); // 清理控制器
|
||||||
|
|
||||||
|
// 构造类似 Response 的对象,支持 headers.get()
|
||||||
|
const headersMap = new Map<string, string>();
|
||||||
|
const headersStr = xhr.getAllResponseHeaders();
|
||||||
|
headersStr.split('\r\n').forEach(line => {
|
||||||
|
const parts = line.split(': ');
|
||||||
|
if (parts.length === 2) {
|
||||||
|
headersMap.set(parts[0].toLowerCase(), parts[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
get: (name: string) => headersMap.get(name.toLowerCase()) || null,
|
||||||
|
has: (name: string) => headersMap.has(name.toLowerCase()),
|
||||||
|
forEach: (callback: (value: string, key: string) => void) => {
|
||||||
|
headersMap.forEach(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
if (xhr.status === 204) {
|
if (xhr.status === 204) {
|
||||||
resolve({ status: 204 });
|
resolve({ status: 204, headers });
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(xhr.responseText);
|
const data = JSON.parse(xhr.responseText);
|
||||||
resolve(data);
|
resolve({ ...data, status: xhr.status, headers });
|
||||||
} catch {
|
} catch {
|
||||||
resolve({ status: xhr.status, raw: xhr.responseText });
|
resolve({ status: xhr.status, raw: xhr.responseText, headers });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -42,13 +94,29 @@ export class Upload {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = () => reject(new Error("Network Error"));
|
xhr.onerror = () => {
|
||||||
|
this.controllers.delete(id);
|
||||||
|
// 如果不是因为中止导致的错误
|
||||||
|
if (!controller.signal.aborted) {
|
||||||
|
reject(new Error("Network Error"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onabort = () => {
|
||||||
|
this.controllers.delete(id);
|
||||||
|
if (controller.signal.aborted) {
|
||||||
|
reject(new DOMException('Upload aborted', 'AbortError'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
xhr.open(method, uploadUrl);
|
xhr.open(method, uploadUrl);
|
||||||
|
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
// PUT 模式:直接上传文件
|
// PUT 模式:直接上传文件
|
||||||
if (file instanceof File) {
|
if (file instanceof File) {
|
||||||
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
||||||
|
} else if (file instanceof Blob) {
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
||||||
}
|
}
|
||||||
xhr.send(file as any);
|
xhr.send(file as any);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -64,32 +132,90 @@ export class Upload {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无进度监听模式(直接 fetch)
|
// 无进度监听模式(直接 fetch)
|
||||||
if (isPut) {
|
try {
|
||||||
// S3 预签名上传
|
let result: Response;
|
||||||
const headers: Record<string, string> = {};
|
|
||||||
if (file instanceof File) {
|
if (isPut) {
|
||||||
headers["Content-Type"] = file.type || "application/octet-stream";
|
// S3 预签名上传
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
if (file instanceof File) {
|
||||||
|
headers["Content-Type"] = file.type || "application/octet-stream";
|
||||||
|
} else if (file instanceof Blob) {
|
||||||
|
headers["Content-Type"] = "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await fetch(uploadUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
headers,
|
||||||
|
body: file as File | Blob,
|
||||||
|
signal: controller.signal, // 添加中止信号
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 表单上传
|
||||||
|
const formData2 = new FormData();
|
||||||
|
for (const key of Object.keys(formData)) {
|
||||||
|
formData2.append(key, formData[key]);
|
||||||
|
}
|
||||||
|
formData2.append(name || "file", file as File);
|
||||||
|
|
||||||
|
result = await fetch(uploadUrl, {
|
||||||
|
method,
|
||||||
|
body: formData2,
|
||||||
|
signal: controller.signal, // 添加中止信号
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await fetch(uploadUrl, {
|
this.controllers.delete(id); // 成功后清理控制器
|
||||||
method: "PUT",
|
|
||||||
headers,
|
|
||||||
body: file as any,
|
|
||||||
});
|
|
||||||
return result;
|
return result;
|
||||||
} else {
|
|
||||||
// 表单上传
|
|
||||||
const formData2 = new FormData();
|
|
||||||
for (const key of Object.keys(formData)) {
|
|
||||||
formData2.append(key, formData[key]);
|
|
||||||
}
|
|
||||||
formData2.append(name || "file", file as File);
|
|
||||||
|
|
||||||
const result = await fetch(uploadUrl, {
|
} catch (error) {
|
||||||
method,
|
this.controllers.delete(id); // 失败后清理控制器
|
||||||
body: formData2,
|
|
||||||
});
|
// 人为中断返回204 使general-business处理成功
|
||||||
return result;
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||||
|
throw new DOMException('Upload aborted', 'AbortError');
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// 中断特定上传
|
||||||
|
abortUpload(uploadId: string): boolean {
|
||||||
|
const controller = this.controllers.get(uploadId);
|
||||||
|
if (controller) {
|
||||||
|
controller.abort();
|
||||||
|
this.controllers.delete(uploadId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中断所有上传
|
||||||
|
abortAllUploads(): void {
|
||||||
|
this.controllers.forEach((controller, id) => {
|
||||||
|
controller.abort();
|
||||||
|
});
|
||||||
|
this.controllers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取上传状态
|
||||||
|
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found' {
|
||||||
|
const controller = this.controllers.get(uploadId);
|
||||||
|
if (!controller) return 'not-found';
|
||||||
|
if (controller.signal.aborted) return 'aborted';
|
||||||
|
return 'uploading';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成唯一的上传ID
|
||||||
|
private generateUploadId(file: File | string | Blob, uploadUrl: string): string {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const random = Math.random().toString(36).substring(2, 9);
|
||||||
|
const fileInfo = file instanceof File ? `${file.name}-${file.size}` : file instanceof Blob ? `blob-${file.size}` : file;
|
||||||
|
return `${uploadUrl}-${fileInfo}-${timestamp}-${random}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有进行中的上传任务
|
||||||
|
getActiveUploads(): string[] {
|
||||||
|
return Array.from(this.controllers.keys());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue