mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-11 18:42:56 +00:00
Merge branch 'feat/0228' of github.com:alibaba/lowcode-engine into feat/0228
This commit is contained in:
commit
bb770b8e5a
@ -71,7 +71,7 @@
|
|||||||
},
|
},
|
||||||
"lifeCycles": {
|
"lifeCycles": {
|
||||||
"componentDidMount": {
|
"componentDidMount": {
|
||||||
"type": "JSExpression",
|
"type": "JSFunction",
|
||||||
"value": "function() { console.log('componentDidMount'); }"
|
"value": "function() { console.log('componentDidMount'); }"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -91,7 +91,7 @@
|
|||||||
"isSync": true
|
"isSync": true
|
||||||
},
|
},
|
||||||
"dataHandler": {
|
"dataHandler": {
|
||||||
"type": "JSExpression",
|
"type": "JSFunction",
|
||||||
"value": "function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}"
|
"value": "function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -105,13 +105,13 @@
|
|||||||
"isSync": true
|
"isSync": true
|
||||||
},
|
},
|
||||||
"dataHandler": {
|
"dataHandler": {
|
||||||
"type": "JSExpression",
|
"type": "JSFunction",
|
||||||
"value": "function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data.result;\n}"
|
"value": "function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data.result;\n}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dataHandler": {
|
"dataHandler": {
|
||||||
"type": "JSExpression",
|
"type": "JSFunction",
|
||||||
"value": "function (dataMap) {\n console.info(\"All datasources loaded:\", dataMap);\n}"
|
"value": "function (dataMap) {\n console.info(\"All datasources loaded:\", dataMap);\n}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -71,7 +71,7 @@
|
|||||||
},
|
},
|
||||||
lifeCycles: {
|
lifeCycles: {
|
||||||
componentDidMount: {
|
componentDidMount: {
|
||||||
type: 'JSExpression',
|
type: 'JSFunction',
|
||||||
value: "function() { console.log('componentDidMount'); }",
|
value: "function() { console.log('componentDidMount'); }",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -91,7 +91,7 @@
|
|||||||
isSync: true,
|
isSync: true,
|
||||||
},
|
},
|
||||||
dataHandler: {
|
dataHandler: {
|
||||||
type: 'JSExpression',
|
type: 'JSFunction',
|
||||||
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}',
|
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -105,13 +105,13 @@
|
|||||||
isSync: true,
|
isSync: true,
|
||||||
},
|
},
|
||||||
dataHandler: {
|
dataHandler: {
|
||||||
type: 'JSExpression',
|
type: 'JSFunction',
|
||||||
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data.result;\n}',
|
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data.result;\n}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dataHandler: {
|
dataHandler: {
|
||||||
type: 'JSExpression',
|
type: 'JSFunction',
|
||||||
value: 'function (dataMap) {\n console.info("All datasources loaded:", dataMap);\n}',
|
value: 'function (dataMap) {\n console.info("All datasources loaded:", dataMap);\n}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -136,6 +136,7 @@ export class ProjectBuilder implements IProjectBuilder {
|
|||||||
const builders = this.createModuleBuilders({
|
const builders = this.createModuleBuilders({
|
||||||
extraContextData: {
|
extraContextData: {
|
||||||
projectRemark: parseResult?.project?.projectRemark,
|
projectRemark: parseResult?.project?.projectRemark,
|
||||||
|
template: this.template,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// Generator Code module
|
// Generator Code module
|
||||||
@ -320,7 +321,7 @@ export class ProjectBuilder implements IProjectBuilder {
|
|||||||
// template: this.template,
|
// template: this.template,
|
||||||
inStrictMode: this.inStrictMode,
|
inStrictMode: this.inStrictMode,
|
||||||
tolerateEvalErrors: true,
|
tolerateEvalErrors: true,
|
||||||
evalErrorsHandler: 'console.error(error)',
|
evalErrorsHandler: '',
|
||||||
...this.extraContextData,
|
...this.extraContextData,
|
||||||
...extraContextData,
|
...extraContextData,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* 低代码引擎的出码模块,负责将编排产出的 Schema 转换成实际可执行的代码。
|
* 低代码引擎的出码模块,负责将编排产出的 Schema 转换成实际可执行的代码。
|
||||||
* 注意:为了保持 API 的稳定性, 这里所有导出的 API 均要显式命名方式导出
|
* 注意:为了保持 API 的稳定性,这里所有导出的 API 均要显式命名方式导出
|
||||||
* (即用 export { xxx } from 'xx' 的方式,不要直接 export * from 'xxx')
|
* (即用 export { xxx } from 'xx' 的方式,不要直接 export * from 'xxx')
|
||||||
* 而且所有导出的 API 务必在 tests/public 中编写单元测试
|
* 而且所有导出的 API 务必在 tests/public 中编写单元测试
|
||||||
*/
|
*/
|
||||||
@ -51,6 +51,7 @@ export default {
|
|||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
common: {
|
common: {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理 ES Module
|
* 处理 ES Module
|
||||||
* @deprecated please use esModule
|
* @deprecated please use esModule
|
||||||
|
|||||||
@ -13,12 +13,12 @@ import {
|
|||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
|
|
||||||
export interface PluginConfig {
|
export interface PluginConfig {
|
||||||
fileType: string;
|
fileType?: string;
|
||||||
implementType: 'inConstructor' | 'insMember' | 'hooks';
|
implementType: 'inConstructor' | 'insMember' | 'hooks';
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
const cfg: PluginConfig = {
|
const cfg: PluginConfig & { fileType: string } = {
|
||||||
fileType: FileType.JSX,
|
fileType: FileType.JSX,
|
||||||
implementType: 'inConstructor',
|
implementType: 'inConstructor',
|
||||||
...config,
|
...config,
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
FileType,
|
FileType,
|
||||||
ICodeStruct,
|
ICodeStruct,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
|
import { getSlotRelativePath } from '../../../utils/pathHelper';
|
||||||
|
|
||||||
export interface PluginConfig {
|
export interface PluginConfig {
|
||||||
fileType: string;
|
fileType: string;
|
||||||
@ -31,9 +32,8 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
|
|||||||
type: ChunkType.STRING,
|
type: ChunkType.STRING,
|
||||||
fileType: cfg.fileType,
|
fileType: cfg.fileType,
|
||||||
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
// TODO: 下面这个路径有没有更好的方式来获取?而非写死
|
|
||||||
content: `
|
content: `
|
||||||
import * as __$$i18n from '../../i18n';
|
import * as __$$i18n from '${getSlotRelativePath({ contextData: next.contextData, from: 'components', to: 'i18n' })}';
|
||||||
`,
|
`,
|
||||||
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,18 +11,18 @@ import {
|
|||||||
FileType,
|
FileType,
|
||||||
ICodeStruct,
|
ICodeStruct,
|
||||||
IContainerInfo,
|
IContainerInfo,
|
||||||
IProjectTemplate,
|
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
|
import { getSlotRelativePath } from '../../../utils/pathHelper';
|
||||||
|
|
||||||
export interface PluginConfig {
|
export interface PluginConfig {
|
||||||
fileType: string;
|
fileType?: string;
|
||||||
|
|
||||||
/** prefer using class property to define utils */
|
/** prefer using class property to define utils */
|
||||||
preferClassProperty?: boolean;
|
preferClassProperty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
const cfg: PluginConfig = {
|
const cfg: PluginConfig & { fileType: string } = {
|
||||||
fileType: FileType.JSX,
|
fileType: FileType.JSX,
|
||||||
...config,
|
...config,
|
||||||
};
|
};
|
||||||
@ -36,26 +36,12 @@ const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) =>
|
|||||||
next.contextData.useRefApi = true;
|
next.contextData.useRefApi = true;
|
||||||
const useRef = !!ir.analyzeResult?.isUsingRef;
|
const useRef = !!ir.analyzeResult?.isUsingRef;
|
||||||
|
|
||||||
// const isSingleComponent = next.contextData?.projectRemark?.isSingleComponent;
|
|
||||||
// const template = next.contextData?.template;
|
|
||||||
|
|
||||||
// function getRelativeUtilsPath(template: IProjectTemplate, isSingleComponent: boolean) {
|
|
||||||
// let relativeUtilsPath = '../../utils';
|
|
||||||
// const utilsPath = template.slots.utils.path;
|
|
||||||
// if (ir.containerType === 'Component') {
|
|
||||||
// // TODO: isSingleComponent
|
|
||||||
// relativeUtilsPath = getRelativePath(template.slots.components.path.join('/'), utilsPath.join('/'));
|
|
||||||
// }
|
|
||||||
// return relativeUtilsPath;
|
|
||||||
// }
|
|
||||||
|
|
||||||
next.chunks.push({
|
next.chunks.push({
|
||||||
type: ChunkType.STRING,
|
type: ChunkType.STRING,
|
||||||
fileType: cfg.fileType,
|
fileType: cfg.fileType,
|
||||||
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
// TODO: 下面这个路径有没有更好的方式来获取?而非写死
|
|
||||||
content: `
|
content: `
|
||||||
import utils${useRef ? ', { RefsManager }' : ''} from '../../utils';
|
import utils${useRef ? ', { RefsManager }' : ''} from '${getSlotRelativePath({ contextData: next.contextData, from: 'components', to: 'utils' })}';
|
||||||
`,
|
`,
|
||||||
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -25,8 +25,7 @@ const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
|||||||
content: `
|
content: `
|
||||||
const i18nConfig = ${i18nStr};
|
const i18nConfig = ${i18nStr};
|
||||||
|
|
||||||
// let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN';
|
let locale = typeof navigator === 'object' && typeof navigator.language === 'string' ? navigator.language : 'zh-CN';
|
||||||
let locale = 'zh-CN';
|
|
||||||
|
|
||||||
const getLocale = () => locale;
|
const getLocale = () => locale;
|
||||||
|
|
||||||
|
|||||||
@ -271,7 +271,7 @@ export function parseExpressionConvertThis2Context(
|
|||||||
|
|
||||||
const localVariablesSet = new Set(localVariables);
|
const localVariablesSet = new Set(localVariables);
|
||||||
|
|
||||||
let thisScopeLevel = -1;
|
let thisScopeLevel = CROSS_THIS_SCOPE_TYPE_NODE[exprAst.type] ? -1 : 0;
|
||||||
traverse(fileAst, {
|
traverse(fileAst, {
|
||||||
enter(path) {
|
enter(path) {
|
||||||
if (CROSS_THIS_SCOPE_TYPE_NODE[path.node.type]) {
|
if (CROSS_THIS_SCOPE_TYPE_NODE[path.node.type]) {
|
||||||
@ -303,7 +303,7 @@ export function parseExpressionConvertThis2Context(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 替换 this (只在顶层替换)
|
// 替换 this (只在顶层替换)
|
||||||
if (thisScopeLevel === 0) {
|
if (thisScopeLevel <= 0) {
|
||||||
obj.replaceWith(t.identifier(contextName));
|
obj.replaceWith(t.identifier(contextName));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -317,7 +317,7 @@ export function parseExpressionConvertThis2Context(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thisScopeLevel === 0) {
|
if (thisScopeLevel <= 0) {
|
||||||
path.replaceWith(t.identifier(contextName));
|
path.replaceWith(t.identifier(contextName));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import * as version from './version';
|
|||||||
import * as scope from './Scope';
|
import * as scope from './Scope';
|
||||||
import * as expressionParser from './expressionParser';
|
import * as expressionParser from './expressionParser';
|
||||||
import * as dataSource from './dataSource';
|
import * as dataSource from './dataSource';
|
||||||
|
import * as pathHelper from './pathHelper';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
common,
|
common,
|
||||||
@ -27,4 +28,5 @@ export {
|
|||||||
scope,
|
scope,
|
||||||
expressionParser,
|
expressionParser,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
pathHelper,
|
||||||
};
|
};
|
||||||
|
|||||||
41
modules/code-generator/src/utils/pathHelper.ts
Normal file
41
modules/code-generator/src/utils/pathHelper.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { IContextData } from '../types';
|
||||||
|
|
||||||
|
function relativePath(from: string[], to: string[]): string[] {
|
||||||
|
const length = Math.min(from.length, to.length);
|
||||||
|
let samePartsLength = length;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
if (from[i] !== to[i]) {
|
||||||
|
samePartsLength = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (samePartsLength === 0) {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
let outputParts = [];
|
||||||
|
for (let i = samePartsLength; i < from.length; i++) {
|
||||||
|
outputParts.push('..');
|
||||||
|
}
|
||||||
|
outputParts = [...outputParts, ...to.slice(samePartsLength)];
|
||||||
|
if (outputParts[0] !== '..') {
|
||||||
|
outputParts.unshift('.');
|
||||||
|
}
|
||||||
|
return outputParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSlotRelativePath(options: {
|
||||||
|
contextData: IContextData;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}) {
|
||||||
|
const { contextData, from, to } = options;
|
||||||
|
const isSingleComponent = contextData?.extraContextData?.projectRemark?.isSingleComponent;
|
||||||
|
const template = contextData?.extraContextData?.template;
|
||||||
|
let toPath = template.slots[to].path;
|
||||||
|
toPath = [...toPath, template.slots[to].fileName!];
|
||||||
|
let fromPath = template.slots[from].path;
|
||||||
|
if (!isSingleComponent && ['components', 'pages'].indexOf(from) !== -1) {
|
||||||
|
fromPath = [...fromPath, 'pageName'];
|
||||||
|
}
|
||||||
|
return relativePath(fromPath, toPath).join('/');
|
||||||
|
}
|
||||||
@ -253,7 +253,6 @@ class Home$$Page extends Component {
|
|||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
throw new Error(response.message);
|
throw new Error(response.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
isInit: true,
|
isInit: true,
|
||||||
@ -280,7 +279,6 @@ class Home$$Page extends Component {
|
|||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
throw new Error(response.message);
|
throw new Error(response.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.result;
|
return response.data.result;
|
||||||
},
|
},
|
||||||
isInit: true,
|
isInit: true,
|
||||||
|
|||||||
@ -31,9 +31,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -71,10 +71,10 @@ class Test$$Page extends React.Component {
|
|||||||
type: 'urlParams',
|
type: 'urlParams',
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
options: function () {
|
options: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'user',
|
id: 'user',
|
||||||
@ -85,17 +85,16 @@ class Test$$Page extends React.Component {
|
|||||||
uri: 'https://shs.xxx.com/mock/1458/demo/user',
|
uri: 'https://shs.xxx.com/mock/1458/demo/user',
|
||||||
isSync: true,
|
isSync: true,
|
||||||
};
|
};
|
||||||
},
|
}.bind(_this),
|
||||||
dataHandler: function (response) {
|
dataHandler: function (response) {
|
||||||
if (!response.data.success) {
|
if (!response.data.success) {
|
||||||
throw new Error(response.data.message);
|
throw new Error(response.data.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
},
|
},
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'orders',
|
id: 'orders',
|
||||||
@ -106,17 +105,16 @@ class Test$$Page extends React.Component {
|
|||||||
uri: 'https://shs.xxx.com/mock/1458/demo/orders',
|
uri: 'https://shs.xxx.com/mock/1458/demo/orders',
|
||||||
isSync: true,
|
isSync: true,
|
||||||
};
|
};
|
||||||
},
|
}.bind(_this),
|
||||||
dataHandler: function (response) {
|
dataHandler: function (response) {
|
||||||
if (!response.data.success) {
|
if (!response.data.success) {
|
||||||
throw new Error(response.data.message);
|
throw new Error(response.data.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.data.result;
|
return response.data.data.result;
|
||||||
},
|
},
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dataHandler: function (dataMap) {
|
dataHandler: function (dataMap) {
|
||||||
|
|||||||
@ -71,7 +71,7 @@
|
|||||||
},
|
},
|
||||||
"lifeCycles": {
|
"lifeCycles": {
|
||||||
"componentDidMount": {
|
"componentDidMount": {
|
||||||
"type": "JSExpression",
|
"type": "JSFunction",
|
||||||
"value": "function() { console.log('componentDidMount'); }"
|
"value": "function() { console.log('componentDidMount'); }"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -34,9 +34,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -67,10 +67,10 @@ class Aaaa$$Page extends React.Component {
|
|||||||
return {
|
return {
|
||||||
uri: '',
|
uri: '',
|
||||||
};
|
};
|
||||||
},
|
}.bind(_this),
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -29,9 +29,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -71,7 +71,7 @@
|
|||||||
},
|
},
|
||||||
"lifeCycles": {
|
"lifeCycles": {
|
||||||
"componentDidMount": {
|
"componentDidMount": {
|
||||||
"type": "JSExpression",
|
"type": "JSFunction",
|
||||||
"value": "function() { console.log('componentDidMount'); }"
|
"value": "function() { console.log('componentDidMount'); }"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -29,9 +29,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -31,9 +31,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -76,7 +76,7 @@ class Test$$Page extends React.Component {
|
|||||||
type: 'fetch',
|
type: 'fetch',
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return true;
|
return true;
|
||||||
},
|
}.bind(_this),
|
||||||
options: function () {
|
options: function () {
|
||||||
return {
|
return {
|
||||||
params: {},
|
params: {},
|
||||||
@ -86,7 +86,7 @@ class Test$$Page extends React.Component {
|
|||||||
headers: {},
|
headers: {},
|
||||||
uri: 'https://mocks.xxx.com/mock/jjpin/user/list',
|
uri: 'https://mocks.xxx.com/mock/jjpin/user/list',
|
||||||
};
|
};
|
||||||
},
|
}.bind(_this),
|
||||||
id: 'users',
|
id: 'users',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -32,9 +32,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -31,9 +31,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -71,10 +71,10 @@ class Test$$Page extends React.Component {
|
|||||||
type: 'urlParams',
|
type: 'urlParams',
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
options: function () {
|
options: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'user',
|
id: 'user',
|
||||||
@ -85,17 +85,16 @@ class Test$$Page extends React.Component {
|
|||||||
uri: 'https://shs.xxx.com/mock/1458/demo/user',
|
uri: 'https://shs.xxx.com/mock/1458/demo/user',
|
||||||
isSync: true,
|
isSync: true,
|
||||||
};
|
};
|
||||||
},
|
}.bind(_this),
|
||||||
dataHandler: function (response) {
|
dataHandler: function (response) {
|
||||||
if (!response.data.success) {
|
if (!response.data.success) {
|
||||||
throw new Error(response.data.message);
|
throw new Error(response.data.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
},
|
},
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'orders',
|
id: 'orders',
|
||||||
@ -106,17 +105,16 @@ class Test$$Page extends React.Component {
|
|||||||
uri: 'https://shs.xxx.com/mock/1458/demo/orders',
|
uri: 'https://shs.xxx.com/mock/1458/demo/orders',
|
||||||
isSync: true,
|
isSync: true,
|
||||||
};
|
};
|
||||||
},
|
}.bind(_this),
|
||||||
dataHandler: function (response) {
|
dataHandler: function (response) {
|
||||||
if (!response.data.success) {
|
if (!response.data.success) {
|
||||||
throw new Error(response.data.message);
|
throw new Error(response.data.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.data.result;
|
return response.data.data.result;
|
||||||
},
|
},
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dataHandler: function (dataMap) {
|
dataHandler: function (dataMap) {
|
||||||
|
|||||||
@ -71,7 +71,7 @@
|
|||||||
},
|
},
|
||||||
lifeCycles: {
|
lifeCycles: {
|
||||||
componentDidMount: {
|
componentDidMount: {
|
||||||
type: 'JSExpression',
|
type: 'JSFunction',
|
||||||
value: "function() { console.log('componentDidMount'); }",
|
value: "function() { console.log('componentDidMount'); }",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -91,7 +91,7 @@
|
|||||||
isSync: true,
|
isSync: true,
|
||||||
},
|
},
|
||||||
dataHandler: {
|
dataHandler: {
|
||||||
type: 'JSExpression',
|
type: 'JSFunction',
|
||||||
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}',
|
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -105,13 +105,13 @@
|
|||||||
isSync: true,
|
isSync: true,
|
||||||
},
|
},
|
||||||
dataHandler: {
|
dataHandler: {
|
||||||
type: 'JSExpression',
|
type: 'JSFunction',
|
||||||
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data.result;\n}',
|
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data.result;\n}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dataHandler: {
|
dataHandler: {
|
||||||
type: 'JSExpression',
|
type: 'JSFunction',
|
||||||
value: 'function (dataMap) {\n console.info("All datasources loaded:", dataMap);\n}',
|
value: 'function (dataMap) {\n console.info("All datasources loaded:", dataMap);\n}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -31,9 +31,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -151,6 +151,7 @@ class Test$$Page extends React.Component {
|
|||||||
|
|
||||||
onOkModifyDialogThird() {
|
onOkModifyDialogThird() {
|
||||||
//第三步 修改 对话框 确定
|
//第三步 修改 对话框 确定
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
currentStep: 0,
|
currentStep: 0,
|
||||||
isModifyDialogVisible: false,
|
isModifyDialogVisible: false,
|
||||||
@ -159,6 +160,7 @@ class Test$$Page extends React.Component {
|
|||||||
|
|
||||||
onCancelModifyDialogThird() {
|
onCancelModifyDialogThird() {
|
||||||
//第三步 修改 对话框 取消
|
//第三步 修改 对话框 取消
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isModifyDialogVisible: false,
|
isModifyDialogVisible: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -30,9 +30,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -63,10 +63,10 @@ class Example$$Page extends React.Component {
|
|||||||
return {
|
return {
|
||||||
uri: 'https://api.example.com/user/list',
|
uri: 'https://api.example.com/user/list',
|
||||||
};
|
};
|
||||||
},
|
}.bind(_this),
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
}.bind(_this),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,9 +30,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -59,14 +59,14 @@ class $$Page extends React.Component {
|
|||||||
id: 'todos',
|
id: 'todos',
|
||||||
isInit: function () {
|
isInit: function () {
|
||||||
return true;
|
return true;
|
||||||
},
|
}.bind(_this),
|
||||||
type: 'jsonp',
|
type: 'jsonp',
|
||||||
options: function () {
|
options: function () {
|
||||||
return {
|
return {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
uri: 'https://a0ee9135-6a7f-4c0f-a215-f0f247ad907d.mock.pstmn.io',
|
uri: 'https://a0ee9135-6a7f-4c0f-a215-f0f247ad907d.mock.pstmn.io',
|
||||||
};
|
};
|
||||||
},
|
}.bind(_this),
|
||||||
dataHandler: function dataHandler(data) {
|
dataHandler: function dataHandler(data) {
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -32,9 +32,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -65,7 +65,6 @@ class Test$$Page extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.__jp__init();
|
this.__jp__init();
|
||||||
|
|
||||||
this.statusDesc = {
|
this.statusDesc = {
|
||||||
0: '失败',
|
0: '失败',
|
||||||
1: '成功',
|
1: '成功',
|
||||||
@ -163,7 +162,6 @@ class Test$$Page extends React.Component {
|
|||||||
if (!item) {
|
if (!item) {
|
||||||
return '暂无结果';
|
return '暂无结果';
|
||||||
}
|
}
|
||||||
|
|
||||||
const { channel, plat, version, status } = item;
|
const { channel, plat, version, status } = item;
|
||||||
return [channel, plat, version, status].join('-');
|
return [channel, plat, version, status].join('-');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,9 +32,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -67,7 +67,6 @@ class Test$$Page extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.__jp__init();
|
this.__jp__init();
|
||||||
|
|
||||||
this.statusDesc = {
|
this.statusDesc = {
|
||||||
0: '失败',
|
0: '失败',
|
||||||
1: '成功',
|
1: '成功',
|
||||||
@ -202,12 +201,10 @@ class Test$$Page extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.$ds.resolve('PROJECTS');
|
this.$ds.resolve('PROJECTS');
|
||||||
|
|
||||||
if (this.userTimeout) {
|
if (this.userTimeout) {
|
||||||
clearTimeout(this.userTimeout);
|
clearTimeout(this.userTimeout);
|
||||||
this.userTimeout = null;
|
this.userTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.projectTimeout) {
|
if (this.projectTimeout) {
|
||||||
clearTimeout(this.projectTimeout);
|
clearTimeout(this.projectTimeout);
|
||||||
this.projectTimeout = null;
|
this.projectTimeout = null;
|
||||||
|
|||||||
@ -25,9 +25,16 @@
|
|||||||
"eslint": "eslint --cache --ext .js,.jsx ./",
|
"eslint": "eslint --cache --ext .js,.jsx ./",
|
||||||
"stylelint": "stylelint ./**/*.scss"
|
"stylelint": "stylelint ./**/*.scss"
|
||||||
},
|
},
|
||||||
"ideMode": { "name": "ice-react" },
|
"ideMode": {
|
||||||
"iceworks": { "type": "react", "adapter": "adapter-react-v3" },
|
"name": "ice-react"
|
||||||
"engines": { "node": ">=8.0.0" },
|
},
|
||||||
|
"iceworks": {
|
||||||
|
"type": "react",
|
||||||
|
"adapter": "adapter-react-v3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
"url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
|
||||||
|
|||||||
@ -19,7 +19,12 @@ export function App() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`postprocessor/prettier should works for json file 1`] = `
|
exports[`postprocessor/prettier should works for json file 1`] = `
|
||||||
"{ \\"components\\": [\\"Button\\", \\"Block\\"] }
|
"{
|
||||||
|
\\"components\\": [
|
||||||
|
\\"Button\\",
|
||||||
|
\\"Block\\"
|
||||||
|
]
|
||||||
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user