openyida 2026.10.23-1 → 2026.10.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/app/services/canvas-page-schema-builder.js +1 -43
- package/lib/core/command-manifest.js +3 -7
- package/lib/core/task-center.js +4 -32
- package/lib/samples/openyida-scaffold/canvas-navigation.jsx +2 -6
- package/lib/task-center/contract.js +1 -26
- package/lib/task-center/loader.js +0 -5
- package/lib/task-center/scaffold.js +0 -9
- package/package.json +1 -1
- package/yida-skills/references/yida-api.md +1 -3
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +0 -2
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +1 -1
- package/yida-skills/skills/yida-canvas-data-binding/references/workflow-capabilities.md +1 -29
- package/yida-skills/skills/yida-task-center/SKILL.md +0 -2
- package/lib/navigation/page-navigation.js +0 -62
- package/lib/task-center/navigation.js +0 -41
package/README.md
CHANGED
|
@@ -534,7 +534,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
534
534
|
| Command | Description |
|
|
535
535
|
|---------|-------------|
|
|
536
536
|
| `openyida data <query\|get\|create\|update\|transfer> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --expect-form-name <name> --expect-form-type receipt --confirm [--json]` | Unified data management (form/process/task/subform) |
|
|
537
|
-
| `openyida task-center <todo\|created\|done\|cc\|proxy\|capabilities\|
|
|
537
|
+
| `openyida task-center <todo\|created\|done\|cc\|proxy\|capabilities\|scaffold\|options\|inspect\|count> [--spec <file.json> --output <file.canvas.jsx>] [options]` | Global task center (todo/processed/cc etc.) |
|
|
538
538
|
| `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
|
|
539
539
|
| `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
|
|
540
540
|
| `openyida read-dingtalk-tingji <taskUuid> [--json]` | Fetch DingTalk Tingji details by task UUID |
|
|
@@ -10,16 +10,7 @@ const {
|
|
|
10
10
|
findEmojiInValue,
|
|
11
11
|
} = require('../../core/no-emoji-detector');
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
const { createTaskCenterContract } = require('../../task-center/contract');
|
|
15
|
-
const { createTaskCenterLoader } = require('../../task-center/loader');
|
|
16
|
-
const { createTaskCenterNavigation } = require('../../task-center/navigation');
|
|
17
|
-
|
|
18
|
-
const CANVAS_YIDA_API_BRIDGE_SOURCE = `${createPageNavigation.toString()}
|
|
19
|
-
${createTaskCenterNavigation.toString()}
|
|
20
|
-
${createTaskCenterContract.toString()}
|
|
21
|
-
${createTaskCenterLoader.toString()}
|
|
22
|
-
var OPENYIDA_YIDA_FORM_API_METHODS = [
|
|
13
|
+
const CANVAS_YIDA_API_BRIDGE_SOURCE = `var OPENYIDA_YIDA_FORM_API_METHODS = [
|
|
23
14
|
'saveFormData',
|
|
24
15
|
'updateFormData',
|
|
25
16
|
'searchFormDataIds',
|
|
@@ -248,15 +239,8 @@ function openyidaCreateRouterBridge(utils) {
|
|
|
248
239
|
|
|
249
240
|
function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
250
241
|
var utils = context && context.utils;
|
|
251
|
-
var navigation = createPageNavigation(window);
|
|
252
|
-
var taskNavigation = createTaskCenterNavigation();
|
|
253
|
-
var taskCounts = createTaskCenterLoader(createTaskCenterContract(), function() { return yidaBridge; });
|
|
254
242
|
var methodNames = openyidaCollectUtilsMethodNames(utils);
|
|
255
243
|
function invoke(methodName) {
|
|
256
|
-
if (methodName === 'openPage') { return navigation.open.apply(null, Array.prototype.slice.call(arguments, 1)); }
|
|
257
|
-
if (['taskCenterCount', 'taskCenterLink', 'openTaskCenter'].indexOf(methodName) !== -1) {
|
|
258
|
-
return bridge[methodName].apply(null, Array.prototype.slice.call(arguments, 1));
|
|
259
|
-
}
|
|
260
244
|
if (!utils || typeof utils[methodName] !== 'function') {
|
|
261
245
|
throw new Error('this.utils.' + methodName + ' is not available');
|
|
262
246
|
}
|
|
@@ -267,8 +251,6 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
|
267
251
|
apiGroups: {
|
|
268
252
|
utility: OPENYIDA_UTIL_API_METHODS.slice(),
|
|
269
253
|
router: ['push'],
|
|
270
|
-
navigation: ['openPage', 'taskCenterLink', 'openTaskCenter'],
|
|
271
|
-
tasks: ['taskCenterCount'],
|
|
272
254
|
yida: OPENYIDA_KNOWN_YIDA_API_METHODS.slice()
|
|
273
255
|
},
|
|
274
256
|
knownMethods: OPENYIDA_UTIL_API_METHODS.slice(),
|
|
@@ -277,33 +259,9 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
|
277
259
|
return !utils || typeof utils[methodName] !== 'function';
|
|
278
260
|
}),
|
|
279
261
|
invoke: invoke,
|
|
280
|
-
openPage: navigation.open,
|
|
281
|
-
taskCenterLink: function(options) {
|
|
282
|
-
return taskNavigation.target(Object.assign({ baseUrl: window.location.origin }, options));
|
|
283
|
-
},
|
|
284
|
-
taskCenterCount: async function(options) {
|
|
285
|
-
if (!options || typeof options !== 'object' || Array.isArray(options)
|
|
286
|
-
|| Object.keys(options).some(function(key) { return ['scope', 'appType', 'kind', 'filters'].indexOf(key) === -1; })) {
|
|
287
|
-
var error = new Error('TASK_FILTER_INVALID: options');
|
|
288
|
-
error.code = 'TASK_FILTER_INVALID';
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
291
|
-
var target = taskNavigation.target({ scope: options.scope, appType: options.appType, kind: options.kind, baseUrl: window.location.origin });
|
|
292
|
-
var filters = createTaskCenterContract().normalizeFilters(target.kind, options.filters === undefined ? {} : options.filters, 'app-query');
|
|
293
|
-
return taskCounts.count(target.kind, Object.assign({}, filters, { transport: 'app-query' },
|
|
294
|
-
target.scope === 'corp' ? { scope: 'corp' } : { appType: target.appType }));
|
|
295
|
-
},
|
|
296
|
-
openTaskCenter: function(options) {
|
|
297
|
-
var target = taskNavigation.target(Object.assign({ baseUrl: window.location.origin }, options));
|
|
298
|
-
return navigation.open(target.pageUrl, { mode: target.mode });
|
|
299
|
-
},
|
|
300
262
|
router: openyidaCreateRouterBridge(utils),
|
|
301
263
|
yida: yidaBridge
|
|
302
264
|
};
|
|
303
|
-
['openPage', 'taskCenterLink', 'openTaskCenter', 'taskCenterCount'].forEach(function(methodName) {
|
|
304
|
-
if (bridge.availableMethods.indexOf(methodName) === -1) { bridge.availableMethods.push(methodName); }
|
|
305
|
-
});
|
|
306
|
-
bridge.missingKnownMethods = bridge.missingKnownMethods.filter(function(name) { return name !== 'openPage'; });
|
|
307
265
|
methodNames.forEach(function(methodName) {
|
|
308
266
|
if (!bridge[methodName]) {
|
|
309
267
|
bridge[methodName] = function() {
|
|
@@ -683,7 +683,7 @@ const COMMAND_SIDE_EFFECTS = new Map([
|
|
|
683
683
|
['task-center', sideEffect('mixed', {
|
|
684
684
|
mutates_yida: false,
|
|
685
685
|
mutates_local: true,
|
|
686
|
-
read_actions: ['todo', 'created', 'done', 'cc', 'proxy', 'capabilities', '
|
|
686
|
+
read_actions: ['todo', 'created', 'done', 'cc', 'proxy', 'capabilities', 'options', 'inspect', 'count'],
|
|
687
687
|
mutating_actions: ['scaffold'],
|
|
688
688
|
})],
|
|
689
689
|
]);
|
|
@@ -1821,11 +1821,10 @@ const COMMAND_GROUPS = [
|
|
|
1821
1821
|
'openyida data transfer task APP_XXX --process-inst-id PINST_XXX --form-uuid FORM_XXX --to-user-id USER_ID --expect-form-name "Approval form" --expect-form-type process --json',
|
|
1822
1822
|
],
|
|
1823
1823
|
}),
|
|
1824
|
-
command('task-center', ['task-center'], 'task-center <todo|created|done|cc|proxy|capabilities|
|
|
1824
|
+
command('task-center', ['task-center'], 'task-center <todo|created|done|cc|proxy|capabilities|scaffold|options|inspect|count> [--spec <file.json> --output <file.canvas.jsx>] [options]', 'help.cmd_task_center', {
|
|
1825
1825
|
requiresLogin: false,
|
|
1826
1826
|
managedRun: { scope: 'actions', actions: {
|
|
1827
1827
|
capabilities: { scope: 'local', allowedFlags: ['--json'] },
|
|
1828
|
-
link: { scope: 'local', allowedFlags: ['--scope', '--type', '--base-url', '--mode', '--json'] },
|
|
1829
1828
|
scaffold: { scope: 'local' },
|
|
1830
1829
|
options: { scope: 'app', appPosition: 1 },
|
|
1831
1830
|
inspect: { scope: 'app', appPosition: 1 },
|
|
@@ -1834,16 +1833,13 @@ const COMMAND_GROUPS = [
|
|
|
1834
1833
|
output: 'json',
|
|
1835
1834
|
examples: [
|
|
1836
1835
|
'openyida task-center capabilities --json',
|
|
1837
|
-
'openyida task-center link APP_XXX --scope app --type todo --base-url https://tenant.aliwork.com --json',
|
|
1838
|
-
'openyida task-center link --scope corp --type cc --base-url https://tenant.aliwork.com --json',
|
|
1839
|
-
'openyida task-center count --scope corp --type created --json',
|
|
1840
1836
|
'openyida task-center options APP_XXX --type originators --keyword 张 --json',
|
|
1841
1837
|
'openyida task-center inspect APP_XXX --process-inst-id INSTANCE_REPLACE --form-uuid FORM_REPLACE --json',
|
|
1842
1838
|
'openyida task-center inspect APP_XXX --process-inst-id INSTANCE_REPLACE --history --task-id TASK_REPLACE --json',
|
|
1843
1839
|
'openyida task-center count APP_XXX --type todo --transport app-query --filters-json \'{"keyword":"采购"}\' --json',
|
|
1844
1840
|
'openyida task-center scaffold --spec workflow-spec.json --output project/pages/src/progress.canvas.jsx',
|
|
1845
1841
|
],
|
|
1846
|
-
notes: ['
|
|
1842
|
+
notes: ['capabilities describes application task kinds, filters, lookup options, workflow read models and scaffold specs. inspect <appType> --process-inst-id ID [--history] [--task-id ID] reads authorized process progress and optional history; taskResult matches a task, without asserting visitor ownership. count <appType> --type KIND [--transport app-query] [--filters-json JSON] reads one list record and its server total; unknown totals stay null. Neither read command requires a custom-page choice. The tasks scaffold selects fields, filterFields and optional expandProcess; the progress view exports ProcessProgressSection for a real linked instance. options <appType> --type originators|processes|forms --keyword TEXT --size N --json reads current-visitor lookup candidates. scaffold creates a new local Canvas page only after an explicit custom-page request or affirmative ask_human choice, recorded as customPage.source and confirmed=true. A generic task-center request or Fast mode does not provide that choice. Pages use the current visitor and existing application theme; CLI credentials are never embedded. Global queries remain unavailable in application-bound managed runs.'],
|
|
1847
1843
|
}),
|
|
1848
1844
|
command('basic-info', ['basic-info'], 'basic-info <overview|commodity|grant|capacity|quota|abs-path|dataflow|i18n|domain>', 'help.cmd_basic_info', { managedRun: { scope: 'denied', reason: 'scope_not_audited' },
|
|
1849
1845
|
output: 'json',
|
package/lib/core/task-center.js
CHANGED
|
@@ -50,13 +50,11 @@ Usage:
|
|
|
50
50
|
--keyword TEXT 搜索关键词
|
|
51
51
|
--no-detail 不返回详情(仅 created 类型支持)
|
|
52
52
|
|
|
53
|
-
openyida task-center link [appType] --scope app|corp --type todo|created|done|cc [--base-url URL] [--mode dingtalk|auto|browser] --json
|
|
54
|
-
openyida task-center count --scope corp --type todo|created|done|cc [--filters-json JSON] --json
|
|
55
53
|
openyida task-center capabilities --json
|
|
56
54
|
openyida task-center scaffold --spec <file.json> --output <file.canvas.jsx>
|
|
57
55
|
openyida task-center options <appType> --type <originators|processes|forms> [--keyword TEXT] [--size N] --json
|
|
58
56
|
openyida task-center inspect <appType> --process-inst-id <id> [--form-uuid <id>] [--history] [--task-id <id>] --json
|
|
59
|
-
openyida task-center count <appType>
|
|
57
|
+
openyida task-center count <appType> --type <todo|created|done|cc> [--transport app-v1|app-query] [--filters-json <json>] --json
|
|
60
58
|
`;
|
|
61
59
|
|
|
62
60
|
// ── 工具函数 ──────────────────────────────────────────
|
|
@@ -219,26 +217,6 @@ async function run(args) {
|
|
|
219
217
|
return result;
|
|
220
218
|
}
|
|
221
219
|
|
|
222
|
-
if (type === 'link') {
|
|
223
|
-
const allowed = ['scope', 'type', 'base_url', 'mode', 'json'];
|
|
224
|
-
const invalid = Object.keys(options).find(key => !allowed.includes(key));
|
|
225
|
-
if (invalid || positionals.length > 2 || options.json !== undefined && options.json !== true) {
|
|
226
|
-
fail(t('task_center.invalid_spec', invalid || 'arguments'));
|
|
227
|
-
}
|
|
228
|
-
if (!['app', 'corp'].includes(options.scope)) { fail(t('task_center.invalid_spec', 'scope')); }
|
|
229
|
-
const baseUrl = options.base_url === undefined ? (await ensureSession()).baseUrl : options.base_url;
|
|
230
|
-
const target = require('../task-center/navigation').target({
|
|
231
|
-
scope: options.scope, kind: options.type, baseUrl, mode: options.mode,
|
|
232
|
-
...(positionals[1] !== undefined ? { appType: positionals[1] } : {}),
|
|
233
|
-
});
|
|
234
|
-
const nativeTarget = require('../task-center/navigation').target({ scope: options.scope, kind: options.type, baseUrl, mode: 'dingtalk',
|
|
235
|
-
...(positionals[1] !== undefined ? { appType: positionals[1] } : {}) });
|
|
236
|
-
const dingtalkUrl = require('../dingtalk/dingtalk-link').buildDingTalkPageLink({ url: nativeTarget.pageUrl });
|
|
237
|
-
const result = { ...target, dingtalkUrl, opened: false };
|
|
238
|
-
console.log(JSON.stringify(result, null, 2));
|
|
239
|
-
return result;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
220
|
if (type === 'options') {
|
|
243
221
|
const invalid = Object.keys(options).find(key => !['type', 'keyword', 'size', 'json'].includes(key));
|
|
244
222
|
if (invalid || positionals.length !== 2) { fail(t('task_center.invalid_spec', invalid || 'arguments')); }
|
|
@@ -256,9 +234,9 @@ async function run(args) {
|
|
|
256
234
|
}
|
|
257
235
|
|
|
258
236
|
if (type === 'inspect' || type === 'count') {
|
|
259
|
-
const allowed = type === 'inspect' ? ['process_inst_id', 'form_uuid', 'task_id', 'history', 'json'] : ['type', 'transport', 'filters_json', '
|
|
237
|
+
const allowed = type === 'inspect' ? ['process_inst_id', 'form_uuid', 'task_id', 'history', 'json'] : ['type', 'transport', 'filters_json', 'json'];
|
|
260
238
|
const invalid = Object.keys(options).find(key => !allowed.includes(key));
|
|
261
|
-
if (invalid ||
|
|
239
|
+
if (invalid || positionals.length !== 2 || options.json !== undefined && options.json !== true || options.history !== undefined && options.history !== true) {
|
|
262
240
|
fail(t('task_center.invalid_spec', invalid || 'arguments'));
|
|
263
241
|
}
|
|
264
242
|
const tasks = require('../task-center/contract');
|
|
@@ -273,13 +251,7 @@ async function run(args) {
|
|
|
273
251
|
if (options.filters_json !== undefined) {
|
|
274
252
|
try { filters = JSON.parse(options.filters_json); } catch { fail(t('task_center.invalid_spec', 'filters-json')); }
|
|
275
253
|
}
|
|
276
|
-
|
|
277
|
-
if (options.scope === 'corp' && positionals[1] !== undefined) { fail(t('task_center.invalid_spec', 'appType')); }
|
|
278
|
-
const transport = options.transport || (options.scope !== undefined ? 'app-query' : undefined);
|
|
279
|
-
input = { ...tasks.normalizeFilters(options.type, filters, transport),
|
|
280
|
-
...(options.scope === 'corp' ? { scope: 'corp' } : { appType: tasks.appOf(positionals[1]) }),
|
|
281
|
-
transport: tasks.transportOf(transport) };
|
|
282
|
-
if (input.scope === 'corp' && input.transport !== 'app-query') { fail(t('task_center.invalid_spec', 'transport')); }
|
|
254
|
+
input = { ...tasks.normalizeFilters(options.type, filters, options.transport), appType: tasks.appOf(positionals[1]), transport: tasks.transportOf(options.transport) };
|
|
283
255
|
}
|
|
284
256
|
const client = createYidaClient({ authRef: await ensureSession() });
|
|
285
257
|
const request = descriptor => client.get(descriptor.path, { _api: 'nattyFetch', _mock: 'false', ...descriptor.params });
|
|
@@ -46,12 +46,8 @@ function navigateCanvasPage(entry, context = {}) {
|
|
|
46
46
|
}
|
|
47
47
|
const href = buildCanvasPageUrl(entry, context);
|
|
48
48
|
const utils = context.utils || window.__OPENYIDA_UTILS__;
|
|
49
|
-
if (entry.openMode === '
|
|
50
|
-
if (typeof utils?.openPage === 'function') return
|
|
51
|
-
? utils.openPage(href, { mode: 'dingtalk' }) : utils.openPage(href);
|
|
52
|
-
if (entry.openMode === 'dingtalk' || /dingtalk/i.test(window.navigator?.userAgent || '')) {
|
|
53
|
-
throw new Error('DINGTALK_API_UNAVAILABLE');
|
|
54
|
-
}
|
|
49
|
+
if (entry.openMode === 'new-tab' || entry.targetType === 'url') {
|
|
50
|
+
if (typeof utils?.openPage === 'function') return utils.openPage({ url: href });
|
|
55
51
|
window.open(href, '_blank', 'noopener,noreferrer');
|
|
56
52
|
return;
|
|
57
53
|
}
|
|
@@ -119,31 +119,6 @@ function createTaskCenterContract() {
|
|
|
119
119
|
return { kind: canonical, transport, method: def.method, endpoint: def.endpoint,
|
|
120
120
|
path: '/dingtalk/web/' + params.appType + '/v1/' + def.endpoint + '.json', params };
|
|
121
121
|
}
|
|
122
|
-
function corpCountRequest(kind, options) {
|
|
123
|
-
const canonical = kindOf(kind);
|
|
124
|
-
if (!options || options.scope !== 'corp' || options.appType !== undefined || options.transport !== 'app-query') {
|
|
125
|
-
fail('TASK_FILTER_INVALID', 'scope');
|
|
126
|
-
}
|
|
127
|
-
const filters = { ...options };
|
|
128
|
-
delete filters.scope;
|
|
129
|
-
delete filters.transport;
|
|
130
|
-
const normalizedFilters = normalizeFilters(canonical, filters, 'app-query');
|
|
131
|
-
return { kind: canonical, transport: 'app-query', path: '/query/task/' + queryDefinitions[canonical].endpoint + '.json',
|
|
132
|
-
params: { _api: 'nattyFetch', _mock: 'false', ...(canonical === 'todo' ? { taskQueryType: 1 } : {}),
|
|
133
|
-
query: JSON.stringify({ page: 1, limit: 1, ...normalizedFilters, appKeys: [] }) }, filters: normalizedFilters };
|
|
134
|
-
}
|
|
135
|
-
function corpCount(payload, kind, filters) {
|
|
136
|
-
checkFailure(payload);
|
|
137
|
-
const page = Object.prototype.hasOwnProperty.call(payload, 'content') ? payload.content : payload;
|
|
138
|
-
checkFailure(page);
|
|
139
|
-
if (!Array.isArray(page.values)) { fail('TASK_RESPONSE_INVALID', 'values'); }
|
|
140
|
-
let total = null;
|
|
141
|
-
if (page.totalCount !== undefined && page.totalCount !== null) {
|
|
142
|
-
if (!/^\d+$/.test(String(page.totalCount)) || !Number.isSafeInteger(Number(page.totalCount))) { fail('TASK_RESPONSE_INVALID', 'totalCount'); }
|
|
143
|
-
total = Number(page.totalCount);
|
|
144
|
-
}
|
|
145
|
-
return { appType: null, kind: kindOf(kind), total, available: total !== null, basis: 'server-total', scope: 'current-visitor-in-corp', filters };
|
|
146
|
-
}
|
|
147
122
|
function text(value) {
|
|
148
123
|
let parsed = value;
|
|
149
124
|
if (typeof value === 'string' && /^\s*\{/.test(value)) {
|
|
@@ -268,7 +243,7 @@ function createTaskCenterContract() {
|
|
|
268
243
|
identity: 'current-visitor', detail: 'native-page', theme: 'existing-application',
|
|
269
244
|
};
|
|
270
245
|
}
|
|
271
|
-
return { kindOf, appOf, transportOf, filterSchema, normalizeFilters, request,
|
|
246
|
+
return { kindOf, appOf, transportOf, filterSchema, normalizeFilters, request, normalize, capabilities, text, outcome, summary, checkFailure };
|
|
272
247
|
}
|
|
273
248
|
|
|
274
249
|
module.exports = { createTaskCenterContract, ...createTaskCenterContract() };
|
|
@@ -50,11 +50,6 @@ function createTaskCenterLoader(contract, resolveBridge, requestJson) {
|
|
|
50
50
|
error.code = 'TASK_FILTER_UNSUPPORTED';
|
|
51
51
|
throw error;
|
|
52
52
|
}
|
|
53
|
-
if (options.scope === 'corp') {
|
|
54
|
-
const request = contract.corpCountRequest(kind, options);
|
|
55
|
-
const payload = await (requestJson || fetchPage)(request);
|
|
56
|
-
return contract.corpCount(payload, kind, request.filters);
|
|
57
|
-
}
|
|
58
53
|
const canonical = contract.kindOf(kind);
|
|
59
54
|
const { appType, transport, ...filters } = options;
|
|
60
55
|
const normalizedFilters = contract.normalizeFilters(canonical, filters, transport);
|
|
@@ -19,12 +19,6 @@ function fail(code, field) {
|
|
|
19
19
|
function capabilities() {
|
|
20
20
|
return {
|
|
21
21
|
...contract.capabilities(),
|
|
22
|
-
navigation: {
|
|
23
|
-
command: 'task-center link [appType] --scope app|corp --type todo|created|done|cc [--base-url URL] [--mode dingtalk|auto|browser] --json',
|
|
24
|
-
scopes: ['app', 'corp'], scopeRequired: true, defaultMode: 'auto',
|
|
25
|
-
runtime: 'window.__OPENYIDA_UTILS__.openTaskCenter({ scope, appType (app only), kind, mode })',
|
|
26
|
-
opensFromCli: false,
|
|
27
|
-
},
|
|
28
22
|
lookups: {
|
|
29
23
|
command: 'task-center options <appType> --type <originators|processes|forms> [--keyword TEXT] [--size N] --json',
|
|
30
24
|
kinds: ['originators', 'processes', 'forms'],
|
|
@@ -36,9 +30,6 @@ function capabilities() {
|
|
|
36
30
|
count: 'task-center count <appType> --type todo|created|done|cc [--transport app-v1|app-query] [--filters-json JSON] --json',
|
|
37
31
|
scope: 'current-visitor-visible-process', historyDefault: false,
|
|
38
32
|
results: { personal: 'personalResult on done tasks', process: 'processResult', task: 'taskResult matched by taskId; not proof of current-visitor ownership' },
|
|
39
|
-
runtimeCount: 'window.__OPENYIDA_UTILS__.taskCenterCount({ scope, appType (app only), kind, filters })',
|
|
40
|
-
scopedCount: 'task-center count [appType] --scope app|corp --type todo|created|done|cc --json',
|
|
41
|
-
corpCount: 'task-center count --scope corp --type todo|created|done|cc [--filters-json JSON] --json',
|
|
42
33
|
countBasis: 'server-total', unknownCount: null, historyFailure: 'explicit-partial-error',
|
|
43
34
|
},
|
|
44
35
|
scaffold: {
|
package/package.json
CHANGED
|
@@ -1109,9 +1109,7 @@ export function didMount() {
|
|
|
1109
1109
|
|
|
1110
1110
|
### openPage
|
|
1111
1111
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
Canvas 页面使用 OpenYida 发布桥 `window.__OPENYIDA_UTILS__.openPage(url)`,默认跟随当前浏览器或钉钉环境;浏览器正常打开页面,钉钉内添加 `ddtab=true` 并调用端内接口。只有明确要求严格端内打开时才传 `{mode:'dingtalk'}`。`mode` 是发布桥的能力;调用原生同名函数时按其原生签名传参。待办数量与入口使用发布桥的 `taskCenterCount / taskCenterLink / openTaskCenter`,参见[个人任务能力](../skills/yida-canvas-data-binding/references/workflow-capabilities.md)。
|
|
1112
|
+
**描述**:打开新页面。在钉钉环境下会使用钉钉 API 打开,体验更友好。
|
|
1115
1113
|
|
|
1116
1114
|
### router.push
|
|
1117
1115
|
|
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
- `isRenderNav=false` 表示导航展示配置,不证明当前页面位于 iframe。不要仅凭该参数修改 `window.top.location`;嵌入方式、跨域和宿主能力需分别验证。
|
|
10
10
|
- 宜搭应用壳根据 `navUuid/formUuid` 和 `workbench` 路径维护选中态;同应用页面应进入 `/{appType}/workbench/{formUuid}` 或对应壳层路由。
|
|
11
11
|
- 发布层会把根级工具注册到 `window.__OPENYIDA_UTILS__`;可用时优先通过 `window.__OPENYIDA_UTILS__.router.push` 做应用内跳转,通过 `window.__OPENYIDA_UTILS__.openPage` 打开外部链接或新窗口场景。
|
|
12
|
-
- `openPage(url, { mode: 'auto' })` 接收 URL 字符串;发布桥兼容旧 `{url}` 参数,但新代码统一传字符串。`auto` 在钉钉内添加 `ddtab=true` 并调用原生接口,失败报错;浏览器环境沿用浏览器打开。`mode:'dingtalk'` 要求钉钉环境并禁止失败后回退系统浏览器,`mode:'browser'` 才显式打开浏览器。调用返回 Promise,点击事件必须捕获并展示错误。
|
|
13
|
-
- 待办卡片使用 `utils.taskCenterCount / taskCenterLink / openTaskCenter`,范围与 CLI 对齐,详见[数量卡片与原生入口](../../yida-canvas-data-binding/references/workflow-capabilities.md)。范围不明先 `ask_human`;不要自行拼跨应用入口或扫描 DOM。
|
|
14
12
|
- `openPage` / `window.open` 更适合外部链接、新标签、钉钉链接、文件预览等场景;同应用页面默认在当前应用壳内切换。
|
|
15
13
|
- 页面隐藏应用导航后,页面内自绘导航壳接管跨视图切换;导航可见时使用平台导航承载同级页面切换。
|
|
16
14
|
- 自绘应用导航按 PRD 安排数量、顺序、分组和任务入口,通常工作台在首位;当前用户的 `getAccessableNavs.json` 结果只用于过滤不可见入口。数据片段和接入规则见 [导航数据来源](../../yida-nav-shell/references/nav-shell-patterns.md#导航数据来源)。
|
|
@@ -7,7 +7,7 @@ description: 为 YidaCodeCanvas 自定义内容接入真实表单、个人流程
|
|
|
7
7
|
|
|
8
8
|
## 核心定位
|
|
9
9
|
|
|
10
|
-
先根据用户描述确定“待办”指什么:对象是个人流程任务还是业务记录,与当前访问者有什么关系,需要哪些信息、筛选、刷新及点击行为。页面、区块、卡片或“待办”名称都不能代替业务含义;只接入需求涉及的能力,不默认增加四类栏目、统计或审批按钮。关键语义无法从上下文确定时,只澄清影响数据来源或权限范围的问题。
|
|
10
|
+
先根据用户描述确定“待办”指什么:对象是个人流程任务还是业务记录,与当前访问者有什么关系,需要哪些信息、筛选、刷新及点击行为。页面、区块、卡片或“待办”名称都不能代替业务含义;只接入需求涉及的能力,不默认增加四类栏目、统计或审批按钮。关键语义无法从上下文确定时,只澄清影响数据来源或权限范围的问题。
|
|
11
11
|
|
|
12
12
|
个人流程任务复用 CLI 的 `todo`(待我处理)、`created`(我创建的普通表单与流程)、`done`(我已处理)、`cc`(抄送我的)契约;先用 `openyida task-center capabilities --json` 查询条件,再按所需类型执行 `openyida data query tasks <appType> --type <类型> --normalized` 核查。需要加载实现时复用 CLI 装配产物中的任务加载与详情助手,保留当前展示布局;发布运行态读取实际访问者的任务,不把 CLI 登录人的结果或凭证嵌入页面。订单待发货、工单待处理等业务记录仍按真实表单字段和权限查询,不用流程任务接口代替。
|
|
13
13
|
|
|
@@ -2,34 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
按业务含义选能力,再沿用当前布局。首页的本人待办使用个人任务查询;采购单详情的审批进度使用该业务记录关联的流程实例;订单待发货等业务状态仍查询业务表单。只读查询不要求选择自定义实现,也不创建页面。新增自定义展示仍须用户显式要求或经 ask_human 明确同意。
|
|
4
4
|
|
|
5
|
-
## 数量卡片与原生入口
|
|
6
|
-
|
|
7
|
-
范围不明确时先 `ask_human` 确认当前应用或本组织所有应用;数量和入口共用同一范围。只需卡片数量与原生入口时,复用下列能力,不必装配自定义任务列表。
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
openyida task-center count APP_REPLACE --scope app --type todo --json
|
|
11
|
-
openyida task-center count --scope corp --type todo --json
|
|
12
|
-
openyida task-center link APP_REPLACE --scope app --type todo --base-url https://example.aliwork.com --json
|
|
13
|
-
openyida task-center link --scope corp --type cc --base-url https://example.aliwork.com --json
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
`link` 只生成可检查的原生地址,`opened=false`;不打开本机浏览器。默认 `mode=auto`,生成普通页面 URL,由页面运行时按访问环境选择打开方式;CLI 本身不推断目标访客环境。显式 `--mode dingtalk` 时 URL 含 `ddtab=true`。另返回 `dingtalkUrl`,仅在用户明确要求从浏览器唤起钉钉时绑定为跨端点击入口。传 `--base-url` 可离线生成;否则读取当前登录组织。受应用约束的 managed run 不允许组织范围查询或覆盖组织域名,不绕过该约束。
|
|
17
|
-
|
|
18
|
-
页面发布层提供以下共用接口(必须用包含此能力的 OpenYida 编译发布):
|
|
19
|
-
|
|
20
|
-
```js
|
|
21
|
-
const utils = window.__OPENYIDA_UTILS__;
|
|
22
|
-
const selection = { scope: 'app', appType: 'APP_REPLACE', kind: 'todo' };
|
|
23
|
-
const count = await utils.taskCenterCount(selection);
|
|
24
|
-
// count.available=false 时显示“未知”;请求报错显示失败,不能显示成 0。
|
|
25
|
-
await utils.openTaskCenter(selection); // 默认跟随浏览器/钉钉环境;放在点击事件中并捕获错误
|
|
26
|
-
// 组织范围:{ scope: 'corp', kind: 'todo' },不要传 appType。
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
`taskCenterCount` 固定同源 `app-query`、实际访问者身份与服务端总数,只读一页一条;可选 `filters` 与 CLI app-query 支持的条件一致,不扫描全历史或嵌入 CLI 身份。`taskCenterLink(selection)` 可读取同一地址。应用范围固定 `appKeys=[appType]`,组织范围固定 `appKeys=[]`。旧版不带 scope 的 CLI count 保留原 transport 默认值;新卡片应显式指定 scope,使 CLI 核查与页面采用同一口径。
|
|
30
|
-
|
|
31
|
-
`openTaskCenter` 默认 `mode:'auto'`:浏览器内直接打开浏览器新页签,钉钉内添加唯一 `ddtab=true` 并调用端内打开接口。浏览器默认打开不能因缺少钉钉环境而报错,也不应默认唤起钉钉。钉钉内接口缺失、调用失败或超时仍须报错,不静默跨到系统浏览器。显式 `mode:'dingtalk'` 是仅限端内的严格模式;跨端唤起用用户明确选择的 `dingtalkUrl` 入口。`mode:'browser'` 显式使用浏览器打开方式。不得用浏览器验证结果宣称钉钉新窗口或免登已验收。四类入口分别使用应用系统导航或平台任务中心原生页签,无需猜测 DOM。
|
|
32
|
-
|
|
33
5
|
## CLI 能力与读取
|
|
34
6
|
|
|
35
7
|
先执行 `openyida task-center capabilities --json`。固定接口、范围校验和返回格式由 CLI 维护,不在页面重新实现请求。以下标识均为待替换占位符,实例必须来自真实业务关联,不能按标题猜测。
|
|
@@ -50,7 +22,7 @@ openyida task-center inspect APP_REPLACE --process-inst-id INSTANCE_REPLACE --hi
|
|
|
50
22
|
|
|
51
23
|
- 个人任务仍为 `todo / created / done / cc`;四类条件和人员、流程、表单候选见[任务筛选示例](../../yida-task-center/references/task-filters.md)。不支持的条件直接报错,不静默删条件。
|
|
52
24
|
- 如果业务需要“我发起且仍在审批中的报销”,而个人任务接口不支持该状态条件,先确认真实流程表单,再选择既有 `data query process` 的服务端状态和发起人条件,并明确实例查询口径。运行态使用实际访问者身份,不能把 CLI 登录人的 ID 固化到页面;不在加载器中自动换接口。
|
|
53
|
-
- `count.total`
|
|
25
|
+
- `count.total` 是当前应用、访问者及所传条件下的服务端总数;`available=false / total=null` 表示未知。不是当前页长度,也不是全历史审批数量。“我创建的”包含普通表单与流程,不能先排除普通表单再把原总数称作审批数量。
|
|
54
26
|
- `inspect` 读取当前访问者有权查看的指定实例,不要求该实例由本人发起。返回 `summary / currentNodes / submittedAt / processFinishedAt / processResult / detailTarget`。范围或实例身份不符直接失败;缺少节点、人员或时间保持未知,不用历史最后一条猜当前环节。
|
|
55
27
|
- 页面运行时优先复用发布层可用的流程 JS-API 桥,仅在方法不可用时使用固定同源读取;桥调用报错不切换通道重试。缓存仅限已识别访问者、目标应用及实例,刷新和返回后失效,不缓存失败历史。
|
|
56
28
|
- `history.state` 为 `not-requested / ready / error`。历史失败时可保留详情并展示明确错误,不能展示成“暂无历史”;实例和历史归属冲突、登录失败仍整体报错。
|
|
@@ -7,8 +7,6 @@ description: 仅在用户明确要求用自定义页面开发待办中心,或
|
|
|
7
7
|
|
|
8
8
|
先核对用户已明确选择自定义实现及目标应用。仅提到待办中心、Fast 模式、已有 Canvas 页面或委托 AI 决定均不算选择;不要自行补确认记录。应用搭建中由 `yida-app` 先 ask_human,明确同意后才交接本技能;用户已显式要求时不重复问。
|
|
9
9
|
|
|
10
|
-
先确认数据与入口范围:当前应用(`scope=app`)还是当前用户在本组织所有应用(`scope=corp`)。用户只说“我的待办”且上下文不能确定范围时,必须通过 `ask_human` 确认,不能因正在编辑某个应用就默认应用范围。用户已明确范围时沿用,不重复询问。数量与点击入口必须使用相同范围;不把组织任务误称为所有组织。
|
|
11
|
-
|
|
12
10
|
按用户描述确定所需个人流程任务及交互,不按整页或区块划分业务能力。仅需接入某项任务数据时走 `yida-canvas-data-binding`,不强制使用本装配;业务记录的待处理状态也由数据绑定处理。
|
|
13
11
|
|
|
14
12
|
需要筛选时按需读取[个人任务筛选示例](references/task-filters.md),包含四类任务的组合条件、候选项查询及初始配置片段。
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Portable: publish injects this exact implementation into the page utility bridge.
|
|
4
|
-
function createPageNavigation(win) {
|
|
5
|
-
function fail(code) {
|
|
6
|
-
const error = new Error(code);
|
|
7
|
-
error.code = code;
|
|
8
|
-
throw error;
|
|
9
|
-
}
|
|
10
|
-
function normalize(input, options) {
|
|
11
|
-
const value = input && typeof input === 'object' && !Array.isArray(input) ? input.url : input;
|
|
12
|
-
if (typeof value !== 'string' || !value.trim()) { fail('PAGE_URL_INVALID'); }
|
|
13
|
-
if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) { fail('PAGE_OPEN_OPTIONS_INVALID'); }
|
|
14
|
-
const mode = (options && options.mode) || 'auto';
|
|
15
|
-
if (!['auto', 'dingtalk', 'browser'].includes(mode)) { fail('PAGE_OPEN_MODE_INVALID'); }
|
|
16
|
-
let url;
|
|
17
|
-
try { url = new URL(value, win.location.href || win.location.origin); } catch { fail('PAGE_URL_INVALID'); }
|
|
18
|
-
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) { fail('PAGE_URL_INVALID'); }
|
|
19
|
-
const inDingTalk = /dingtalk/i.test((win.navigator && win.navigator.userAgent) || '');
|
|
20
|
-
const useDingTalk = mode === 'dingtalk' || (mode === 'auto' && inDingTalk);
|
|
21
|
-
if (useDingTalk) { url.searchParams.set('ddtab', 'true'); }
|
|
22
|
-
return { url: url.href, mode, inDingTalk, useDingTalk };
|
|
23
|
-
}
|
|
24
|
-
function invokeNative(invoke, url) {
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
function success(value) { clearTimeout(timer); resolve(value); }
|
|
27
|
-
function rejectOpen(reason) {
|
|
28
|
-
clearTimeout(timer);
|
|
29
|
-
const error = new Error('DINGTALK_OPEN_FAILED');
|
|
30
|
-
error.code = 'DINGTALK_OPEN_FAILED';
|
|
31
|
-
error.cause = reason;
|
|
32
|
-
reject(error);
|
|
33
|
-
}
|
|
34
|
-
const timer = setTimeout(() => rejectOpen('timeout'), 15000);
|
|
35
|
-
try {
|
|
36
|
-
const result = invoke({ url, onSuccess: success, onFail: rejectOpen });
|
|
37
|
-
if (result && typeof result.then === 'function') { result.then(success, rejectOpen); }
|
|
38
|
-
} catch (error) { rejectOpen(error); }
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
async function open(input, options) {
|
|
42
|
-
const target = normalize(input, options);
|
|
43
|
-
if (!target.useDingTalk) {
|
|
44
|
-
win.open(target.url, '_blank', 'noopener,noreferrer');
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (!target.inDingTalk) { fail('DINGTALK_ENV_REQUIRED'); }
|
|
48
|
-
// Use the publishing host's bridge. Do not scan parent DOM or cross-origin windows.
|
|
49
|
-
const dd = win.dd;
|
|
50
|
-
if (dd && dd.env && dd.env.platform === 'notInDingTalk') { fail('DINGTALK_API_UNAVAILABLE'); }
|
|
51
|
-
if (dd && dd.biz && dd.biz.util && typeof dd.biz.util.openLink === 'function') {
|
|
52
|
-
return invokeNative(options => dd.biz.util.openLink(options), target.url);
|
|
53
|
-
}
|
|
54
|
-
if (win.Ali && typeof win.Ali.pushWindow === 'function') {
|
|
55
|
-
return invokeNative(options => win.Ali.pushWindow(options), target.url);
|
|
56
|
-
}
|
|
57
|
-
fail('DINGTALK_API_UNAVAILABLE');
|
|
58
|
-
}
|
|
59
|
-
return { normalize, open };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
module.exports = { createPageNavigation };
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Shared by the CLI and the published page bridge; scope is always explicit.
|
|
4
|
-
function createTaskCenterNavigation() {
|
|
5
|
-
const kinds = {
|
|
6
|
-
todo: { tab: 'running', nav: 'NAV-SYSTEM-RUNNING-UUID' },
|
|
7
|
-
created: { tab: 'fromMe', nav: 'NAV-SYSTEM-FROM-ME-UUID' },
|
|
8
|
-
done: { tab: 'done', nav: 'NAV-SYSTEM-DONE-UUID' },
|
|
9
|
-
cc: { tab: 'notifyMe', nav: 'NAV-SYSTEM-NOTIFY-ME-UUID' },
|
|
10
|
-
};
|
|
11
|
-
function fail(field) {
|
|
12
|
-
const error = new Error('TASK_LINK_INVALID: ' + field);
|
|
13
|
-
error.code = 'TASK_LINK_INVALID';
|
|
14
|
-
throw error;
|
|
15
|
-
}
|
|
16
|
-
function target(input) {
|
|
17
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) { fail('options'); }
|
|
18
|
-
for (const key of Object.keys(input)) {
|
|
19
|
-
if (!['scope', 'appType', 'kind', 'baseUrl', 'mode'].includes(key)) { fail(key); }
|
|
20
|
-
}
|
|
21
|
-
const { scope, appType } = input;
|
|
22
|
-
const kind = input.kind === 'submitted' ? 'created' : input.kind;
|
|
23
|
-
if (!Object.prototype.hasOwnProperty.call(kinds, kind)) { fail('kind'); }
|
|
24
|
-
if (!['app', 'corp'].includes(scope)) { fail('scope'); }
|
|
25
|
-
if (scope === 'app' && (typeof appType !== 'string' || !/^APP_[A-Za-z0-9_-]+$/.test(appType))) { fail('appType'); }
|
|
26
|
-
if (scope === 'corp' && appType !== undefined) { fail('appType'); }
|
|
27
|
-
let origin;
|
|
28
|
-
try { origin = new URL(input.baseUrl); } catch { fail('baseUrl'); }
|
|
29
|
-
if (!['http:', 'https:'].includes(origin.protocol) || origin.username || origin.password) { fail('baseUrl'); }
|
|
30
|
-
const mode = input.mode === undefined ? 'auto' : input.mode;
|
|
31
|
-
if (!['auto', 'dingtalk', 'browser'].includes(mode)) { fail('mode'); }
|
|
32
|
-
const definition = kinds[kind];
|
|
33
|
-
const url = new URL(scope === 'app' ? '/' + appType + '/workbench/' + definition.nav : '/taskCenter.html', origin.origin);
|
|
34
|
-
if (scope === 'app') { url.searchParams.set('showTodoTab', 'true'); } else { url.searchParams.set('tab', definition.tab); }
|
|
35
|
-
if (mode === 'dingtalk') { url.searchParams.set('ddtab', 'true'); }
|
|
36
|
-
return { scope, appType: scope === 'app' ? appType : null, kind, pageUrl: url.href, mode };
|
|
37
|
-
}
|
|
38
|
-
return { target };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
module.exports = { createTaskCenterNavigation, ...createTaskCenterNavigation() };
|