修改了user相关的一些页面和上下文中增加了allowUserUpdate的接口

This commit is contained in:
Xu Chang 2022-12-14 18:06:00 +08:00
parent 844729b0ce
commit 807321ac2f
39 changed files with 565 additions and 382 deletions

View File

@ -13,7 +13,7 @@ var checkers = [
},
{
type: 'relation',
action: ['play', 'remove', 'disable', 'enable'],
action: ['remove', 'disable', 'enable'],
entity: 'user',
relationFilter: function () {
// 只有root才能进行操作
@ -22,17 +22,17 @@ var checkers = [
errMsg: '越权操作',
},
{
type: 'data',
action: 'play',
type: 'relation',
action: ['play'],
entity: 'user',
checker: function (data) {
// 不记得什么意思了
/* const token = context.getToken();
const { userId } = token!;
if (userId === operation.filter!.id) {
throw new OakRowInconsistencyException();
} */
relationFilter: function (operation, context) {
if (!context.isReallyRoot()) {
// 只有root才能进行操作
throw new types_1.OakUserUnpermittedException();
}
return undefined;
},
errMsg: '越权操作'
},
{
type: 'data',

View File

@ -5,15 +5,13 @@ exports.default = OakComponent({
properties: {
actions: Array,
actionDescriptions: Object,
show: {
type: Boolean,
value: false,
},
iconSize: String,
},
methods: {
onClick: function (action) {
var onActionClick = this.props.onActionClick;
onActionClick(action);
onClickMp: function (e) {
var index = e.detail.index;
var action = this.props.actions[index];
this.triggerEvent('action', { action: action });
},
},
observers: {

View File

@ -1,6 +1,7 @@
{
"component": true,
"usingComponents": {
"oak-icon": "../../icon/index",
"l-button": "../../../miniprogram_npm/lin-ui/button/index",
"l-icon": "../../../miniprogram_npm/lin-ui/icon/index",
"l-grid": "../../../miniprogram_npm/lin-ui/grid/index",

View File

@ -1,24 +1,10 @@
@import "../../../config/styles/mp/index.less";
@import "../../../config/styles/mp/mixins.less";
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
}
.grid-item {
min-width: 25%;
padding: 20rpx;
display: flex;
flex-direction: column;
justify-content: center;
box-sizing: border-box;
}
.block {
width: 176rpx;
height: 176rpx;
background: #fff;
background: transparent;
color: #333;
display: flex;
}
@ -36,20 +22,9 @@
flex-direction: column;
padding: 32rpx;
}
.five-grid {
position: unset;
}
.external-class-content {
padding: 32rpx 0 !important;
}
.image-icon {
width: 96rpx !important;
height: 96rpx !important;
}
.image {
width: 100%;
height: 100%;
.label {
margin-top: 16rpx;
font-size: xx-small;
color: @oak-color-primary;
}

View File

@ -8,7 +8,6 @@ exports.default = OakComponent({
},
methods: {
printDebugStore: function (e) {
console.log(e);
console.log(this.features.cache.getFullData());
},
printCachedStore: function () {

View File

@ -9,6 +9,7 @@ export declare class BackendRuntimeContext<ED extends EntityDict> extends AsyncC
private application?;
private token?;
private amIRoot?;
private amIReallyRoot?;
private rootMode?;
protected initialize(data?: SerializedData): Promise<void>;
getApplicationId(): ED["application"]["Schema"]["id"] | undefined;
@ -19,5 +20,7 @@ export declare class BackendRuntimeContext<ED extends EntityDict> extends AsyncC
getCurrentUserId(allowUnloggedIn?: boolean): string;
toString(): string;
isRoot(): boolean;
isReallyRoot(): boolean;
sendMessage(data: ED['message']['CreateSingle']['data']): Promise<import("oak-domain/lib/types").OperationResult<ED>>;
allowUserUpdate(): boolean;
}

View File

@ -17,17 +17,17 @@ var BackendRuntimeContext = /** @class */ (function (_super) {
}
BackendRuntimeContext.prototype.initialize = function (data) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var appId, tokenValue, result, result, token, user, _a, userState, userRole$user, err_1;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
var appId, tokenValue, result, result, token, user, player, userRole$user, userRole$player, err_1;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!data) return [3 /*break*/, 11];
return [4 /*yield*/, this.begin()];
case 1:
_b.sent();
_b.label = 2;
_a.sent();
_a.label = 2;
case 2:
_b.trys.push([2, 8, , 10]);
_a.trys.push([2, 8, , 10]);
appId = data.a, tokenValue = data.t;
if (!appId) return [3 /*break*/, 4];
return [4 /*yield*/, this.select('application', {
@ -61,10 +61,10 @@ var BackendRuntimeContext = /** @class */ (function (_super) {
blockTrigger: true,
})];
case 3:
result = _b.sent();
result = _a.sent();
(0, assert_1.default)(result.length > 0, "\u6784\u5EFABackendRuntimeContext\u5BF9\u5E94appId\u300C".concat(appId, "\u300D\u627E\u4E0D\u5230application"));
this.application = result[0];
_b.label = 4;
_a.label = 4;
case 4:
if (!tokenValue) return [3 /*break*/, 6];
return [4 /*yield*/, this.select('token', {
@ -72,6 +72,22 @@ var BackendRuntimeContext = /** @class */ (function (_super) {
id: 1,
userId: 1,
playerId: 1,
player: {
id: 1,
userState: 1,
userRole$user: {
$entity: 'userRole',
data: {
id: 1,
userId: 1,
roleId: 1,
role: {
id: 1,
name: 1,
}
},
},
},
ableState: 1,
user: {
id: 1,
@ -98,7 +114,7 @@ var BackendRuntimeContext = /** @class */ (function (_super) {
blockTrigger: true,
})];
case 5:
result = _b.sent();
result = _a.sent();
if (result.length === 0) {
console.log("\u6784\u5EFABackendRuntimeContext\u5BF9\u5E94tokenValue\u300C".concat(tokenValue, "\u627E\u4E0D\u5230\u76F8\u5173\u7684user"));
throw new Exception_1.OakTokenExpiredException();
@ -107,29 +123,28 @@ var BackendRuntimeContext = /** @class */ (function (_super) {
if (token.ableState === 'disabled') {
throw new Exception_1.OakTokenExpiredException();
}
user = token.user;
_a = user, userState = _a.userState, userRole$user = _a.userRole$user;
/* if (['disabled', 'merged'].includes(userState as string)) {
throw new OakUserDisabledException();
} */
user = token.user, player = token.player;
userRole$user = user.userRole$user;
this.amIRoot = userRole$user.length > 0 && userRole$user.find(function (ele) { return ele.role.name === 'root'; });
userRole$player = player.userRole$user;
this.amIReallyRoot = userRole$player.length > 0 && userRole$player.find(function (ele) { return ele.role.name === 'root'; });
this.token = token;
_b.label = 6;
_a.label = 6;
case 6: return [4 /*yield*/, this.commit()];
case 7:
_b.sent();
_a.sent();
return [3 /*break*/, 10];
case 8:
err_1 = _b.sent();
err_1 = _a.sent();
return [4 /*yield*/, this.rollback()];
case 9:
_b.sent();
_a.sent();
throw err_1;
case 10: return [3 /*break*/, 12];
case 11:
// 否则是后台模式默认用root
this.rootMode = true;
_b.label = 12;
_a.label = 12;
case 12: return [2 /*return*/];
}
});
@ -188,6 +203,9 @@ var BackendRuntimeContext = /** @class */ (function (_super) {
}
return !!this.amIRoot;
};
BackendRuntimeContext.prototype.isReallyRoot = function () {
return !!this.amIReallyRoot;
};
BackendRuntimeContext.prototype.sendMessage = function (data) {
return this.operate('message', {
action: 'create',
@ -196,6 +214,24 @@ var BackendRuntimeContext = /** @class */ (function (_super) {
dontCollect: true,
});
};
BackendRuntimeContext.prototype.allowUserUpdate = function () {
var _a;
var userInfo = (_a = this.token) === null || _a === void 0 ? void 0 : _a.user;
if (userInfo) {
var userState = userInfo.userState;
if (userState === 'disabled') {
throw new Exception_1.OakUserDisabledException('您的帐号已经被禁用,请联系客服');
}
else if (['shadow', 'merged'].includes(userState)) {
throw new Exception_1.OakTokenExpiredException('您的登录状态有异常,请重新登录 ');
}
else {
(0, assert_1.default)(userState === 'normal');
}
return true;
}
throw new Exception_2.OakUnloggedInException('您尚未登录');
};
return BackendRuntimeContext;
}(AsyncRowStore_1.AsyncContext));
exports.BackendRuntimeContext = BackendRuntimeContext;

View File

@ -23,5 +23,7 @@ export declare class FrontendRuntimeContext<ED extends EntityDict, Cxt extends B
getCurrentUserId(allowUnloggedIn?: boolean): string | undefined;
toString(): string;
isRoot(): boolean;
isReallyRoot(): boolean;
allowUserUpdate(): boolean;
}
export {};

View File

@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.FrontendRuntimeContext = void 0;
var tslib_1 = require("tslib");
var SyncRowStore_1 = require("oak-domain/lib/store/SyncRowStore");
var Exception_1 = require("../types/Exception");
var console_1 = require("console");
var types_1 = require("oak-domain/lib/types");
var FrontendRuntimeContext = /** @class */ (function (_super) {
tslib_1.__extends(FrontendRuntimeContext, _super);
function FrontendRuntimeContext(store, application, token) {
@ -57,6 +60,28 @@ var FrontendRuntimeContext = /** @class */ (function (_super) {
var _a;
return ((_a = this.token) === null || _a === void 0 ? void 0 : _a.isRoot()) || false;
};
FrontendRuntimeContext.prototype.isReallyRoot = function () {
var _a;
return ((_a = this.token) === null || _a === void 0 ? void 0 : _a.isReallyRoot()) || false;
};
FrontendRuntimeContext.prototype.allowUserUpdate = function () {
var _a;
var userInfo = (_a = this.token) === null || _a === void 0 ? void 0 : _a.getUserInfo();
if (userInfo) {
var userState = userInfo.userState;
if (userState === 'disabled') {
throw new Exception_1.OakUserDisabledException('您的帐号已经被禁用,请联系客服');
}
else if (['shadow', 'merged'].includes(userState)) {
throw new Exception_1.OakTokenExpiredException('您的登录状态有异常,请重新登录 ');
}
else {
(0, console_1.assert)(userState === 'normal');
}
return true;
}
throw new types_1.OakUnloggedInException('您尚未登录');
};
return FrontendRuntimeContext;
}(SyncRowStore_1.SyncContext));
exports.FrontendRuntimeContext = FrontendRuntimeContext;

View File

@ -6,4 +6,5 @@ export interface RuntimeContext {
getToken(allowUnloggedIn?: boolean): Partial<EntityDict['token']['Schema']> | undefined;
getTokenValue(allowUnloggedIn?: boolean): string | undefined;
isRoot(): boolean;
isReallyRoot(): boolean;
}

View File

@ -22,5 +22,10 @@ export declare class Token<ED extends EntityDict, Cxt extends BackendRuntimeCont
getUserId(allowUnloggedIn?: boolean): NonNullable<ED["token"]["Schema"]["userId"]> | undefined;
getUserInfo(): ED["token"]["Schema"]["user"] | undefined;
isRoot(): boolean;
/**
* token的player到底是不是root
* @returns
*/
isReallyRoot(): boolean;
sendCaptcha(mobile: string): Promise<string>;
}

View File

@ -39,6 +39,14 @@ var userProjection = {
userId: 1,
},
},
userRole$user: {
$entity: 'userRole',
data: {
id: 1,
userId: 1,
roleId: 1,
},
},
};
var tokenProjection = {
id: 1,
@ -227,6 +235,18 @@ var Token = /** @class */ (function (_super) {
}
};
Token.prototype.isRoot = function () {
var _a;
var token = this.getToken(true);
var userRole$user = (_a = token === null || token === void 0 ? void 0 : token.user) === null || _a === void 0 ? void 0 : _a.userRole$user;
return !!(userRole$user &&
(userRole$user === null || userRole$user === void 0 ? void 0 : userRole$user.length) > 0 &&
userRole$user.find(function (ele) { return ele.roleId === constants_1.ROOT_ROLE_ID; }));
};
/**
* 这个是指token的player到底是不是root
* @returns
*/
Token.prototype.isReallyRoot = function () {
var _a;
var token = this.getToken(true);
var userRole$user = (_a = token === null || token === void 0 ? void 0 : token.player) === null || _a === void 0 ? void 0 : _a.userRole$user;

View File

@ -91,31 +91,34 @@ exports.default = OakComponent({
actionDescriptions: {
accept: {
icon: {
name: 'pan_tool',
name: 'circle-check',
type: 'far',
},
label: '通过',
},
activate: {
icon: {
name: 'check',
name: 'chart-line',
},
label: '激活',
},
disable: {
icon: {
name: 'flash_off',
name: 'bell-slash',
type: 'far',
},
label: '禁用',
},
enable: {
icon: {
name: 'flash_on',
name: 'bell',
type: 'far',
},
label: '启用',
},
remove: {
icon: {
name: 'clear',
name: 'trash',
},
label: '删除',
},
@ -127,13 +130,13 @@ exports.default = OakComponent({
},
verify: {
icon: {
name: 'how_to_reg',
name: 'certificate',
},
label: '验证',
},
play: {
icon: {
name: 'play_circle',
name: 'person-praying',
},
label: '切换',
},
@ -184,5 +187,9 @@ exports.default = OakComponent({
});
});
},
onActionClickMp: function (e) {
var action = e.detail.action;
return this.onActionClick(action);
}
},
});

View File

@ -32,6 +32,40 @@
padding: 0rpx 10rpx;
border-bottom: 1px solid #e0e0e0;
background-color: #fff;
.left {
display: flex;
flex-direction: row;
}
.icon {
width: 16rpx;
margin-left: 6rpx;
margin-right: 26rpx;
text-align: center;
}
.label {
color: @oak-text-color-primary;
}
.value {
color: @oak-text-color-secondary;
margin-right: 16rpx;
.primary {
background-color: @oak-color-primary;
}
.success {
background-color: @oak-color-success;
}
.danger {
background-color: @oak-color-error;
}
.warning {
background-color: @oak-color-warning;
}
}
}

View File

@ -60,10 +60,10 @@
font-size: small;
.label {
min-width: 140rpx;
color: @oak-text-color-secondary;
color: @oak-text-color-primary;
}
.value {
color: @oak-text-color-primary;
color: @oak-text-color-secondary;
margin-left: 30rpx;
}
}

View File

@ -20,11 +20,6 @@ var IDCardTypeOptions = [
value: 'Mainland-passport', label: '港澳通行证',
}
];
var PICKER_KEY = {
SEX: 'sex',
IDCARD: 'idCard',
BIRTH: 'birth'
};
exports.default = OakComponent({
entity: 'user',
projection: {
@ -41,89 +36,81 @@ exports.default = OakComponent({
formData: function (_a) {
var user = _a.data;
var _b = user || {}, birth = _b.birth, gender = _b.gender, idCardType = _b.idCardType;
var birthText = birth && new Date(birth).toLocaleDateString();
var birthDate = birth && new Date(birth);
var birthText = birthDate && birthDate.toLocaleDateString();
var birthDayValue = birthDate && "".concat(birthDate.getFullYear(), "-").concat(birthDate.getMonth() + 1, "-").concat(birthDate.getDate());
var GenderDict = {
male: '男',
female: '女',
};
var genderText = gender && GenderDict[gender];
var genderIndex = gender && GenderOptions.find(function (ele) { return ele.value === gender; });
var IdCardTypeDict = {
'ID-Card': '身份证',
passport: '护照',
'Mainland-passport': '港澳通行证',
};
var idCardTypeText = idCardType && IdCardTypeDict[idCardType];
var genderOption = gender && GenderOptions.find(function (ele) { return ele.value === gender; });
var genderText = genderOption && genderOption.label;
var genderOptionIndex = genderOption && GenderOptions.indexOf(genderOption);
var idCardTypeOption = idCardType && IDCardTypeOptions.find(function (ele) { return ele.value === idCardType; });
var idCardTypeText = idCardTypeOption && idCardTypeOption.label;
var idCardTypeOptionIndex = idCardTypeOption && IDCardTypeOptions.indexOf(idCardTypeOption);
var idCardTypeIndex = idCardType && IDCardTypeOptions.find(function (ele) { return ele.value === gender; });
var now = new Date();
return Object.assign({}, user, {
birthText: birthText,
birthDayValue: birthDayValue,
genderText: genderText,
idCardTypeText: idCardTypeText,
idCardTypeOptionIndex: idCardTypeOptionIndex,
oldestBirthday: "".concat(now.getFullYear() - 120, "-01-01"),
today: "".concat(now.getFullYear(), "-").concat(now.getMonth() + 1, "-").concat(now.getDate()),
genderIndex: genderIndex,
genderOptionIndex: genderOptionIndex,
idCardTypeIndex: idCardTypeIndex,
});
},
data: {
birthEnd: '',
GenderOptions: GenderOptions,
IDCardTypeOptions: IDCardTypeOptions,
PICKER_KEY: PICKER_KEY,
birthVisible: false,
},
lifetimes: {
ready: function () {
var today = new Date();
var birthEnd = "".concat(today.getFullYear(), "-").concat(today.getMonth() + 1, "-").concat(today.getDate());
this.setState({ birthEnd: birthEnd });
}
},
methods: {
setValue: function (input) {
var _a, _b, _c;
console.log(input, 123);
var _d = this.resolveInput(input), dataset = _d.dataset, value = _d.value;
var key = dataset.key;
if (['sex', 'idCard'].includes(key)) {
this.update((_a = {}, _a[dataset.attr] = value[0], _a));
return;
}
if (key === 'birth') {
this.update((_b = {}, _b[dataset.attr] = value, _b));
this.setState({
birthVisible: false,
});
return;
}
this.update((_c = {}, _c[dataset.attr] = value[0], _c));
setValueMp: function (input) {
var _a;
var detail = input.detail, dataset = input.target.dataset;
var attr = dataset.attr;
var value = detail.value;
this.update((_a = {}, _a[attr] = value, _a));
},
confirm: function () {
confirmMp: function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.execute()];
case 1:
_a.sent();
if (this.props.oakFrom === 'user:manage:list') {
this.navigateBack();
}
else if (this.props.oakFrom === 'user:manage:detail') {
this.navigateBack();
}
this.navigateBack();
return [2 /*return*/];
}
});
});
},
onClickPicker: function (e) {
var _a;
var _b;
var key = ((_b = e === null || e === void 0 ? void 0 : e.currentTarget) === null || _b === void 0 ? void 0 : _b.dataset).key;
this.setState((_a = {},
_a["".concat(key, "Visible")] = true,
_a));
onBirthChange: function (e) {
var value = e.detail.value;
var birth = new Date(value);
this.update({ birth: birth });
},
onPickerClose: function (e) {
var _a;
var _b;
var key = ((_b = e === null || e === void 0 ? void 0 : e.currentTarget) === null || _b === void 0 ? void 0 : _b.dataset).key;
this.setState((_a = {},
_a["".concat(key, "Visible")] = false,
_a));
onIdCardTypeChange: function (e) {
var index = e.detail.value;
var value = IDCardTypeOptions[index].value;
this.update({ idCardType: value });
},
onGenderChange: function (e) {
var index = e.detail.value;
var value = GenderOptions[index].value;
this.update({ gender: value });
},
},
});

View File

@ -1,12 +1,9 @@
{
"navigationBarTitleText": "修改用户信息",
"usingComponents": {
"t-button": "../../../../miniprogram_npm/tdesign/button/button",
"t-picker": "../../../../miniprogram_npm/tdesign/picker/picker",
"t-picker-item": "../../../../miniprogram_npm/tdesign/picker/picker-item",
"t-date-time-picker": "../../../../miniprogram_npm/tdesign/date-time-picker/date-time-picker",
"t-cell": "../../../../miniprogram_npm/tdesign/cell/cell",
"t-input": "../../../../miniprogram_npm/tdesign/input/input",
"t-icon": "../../../../miniprogram_npm/tdesign/icon/icon"
"l-button": "../../../../miniprogram_npm/lin-ui/button/index",
"l-list": "../../../../miniprogram_npm/lin-ui/list/index",
"l-input": "../../../../miniprogram_npm/lin-ui/input/index",
"l-icon": "../../../../miniprogram_npm/lin-ui/icon/index"
}
}

View File

@ -16,22 +16,8 @@
display: flex;
flex-direction: column;
background-color: #fff;
}
.pannel-item {
display: flex;
align-items: center;
height: 96rpx;
padding: 0 32rpx;
font-size: 32rpx;
background-color: #fff;
}
.pannel-label {
width: 160rpx;
margin-right: 32rpx;
.label {
color: @oak-text-color-primary;
}
}
.pannel-text {
flex: 1;
opacity: 0.9;
}

View File

@ -5,6 +5,7 @@ var uuid_1 = require("oak-domain/lib/utils/uuid");
var assert_1 = require("oak-domain/lib/utils/assert");
var constants_1 = require("../constants");
var randomUser_1 = require("../utils/randomUser");
var types_1 = require("oak-domain/lib/types");
var NO_ANY_USER = true;
var triggers = [
{
@ -116,16 +117,20 @@ var triggers = [
entity: 'user',
action: 'play',
when: 'after',
fn: function (_a, context) {
fn: function (_a, context, option) {
var operation = _a.operation;
return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var filter, id;
var filter, token, id, userId;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
filter = operation.filter;
(0, assert_1.assert)(filter.id);
id = context.getToken().id;
token = context.getToken();
id = token.id, userId = token.userId;
if (userId === filter.id) {
throw new types_1.OakRowInconsistencyException(undefined, '您已经是当前用户');
}
return [4 /*yield*/, context.operate('token', {
id: (0, uuid_1.generateNewId)(),
action: 'update',
@ -135,9 +140,7 @@ var triggers = [
filter: {
id: id,
}
}, {
dontCollect: true,
})];
}, option)];
case 1:
_b.sent();
return [2 /*return*/, 1];

View File

@ -14,26 +14,26 @@ const checkers: Checker<EntityDict, 'user', RuntimeCxt> [] = [
},
{
type: 'relation',
action: ['play', 'remove', 'disable', 'enable'],
action: ['remove', 'disable', 'enable'],
entity: 'user',
relationFilter: () => {
// 只有root才能进行操作
throw new OakUserUnpermittedException();
},
errMsg: '越权操作',
},
},
{
type: 'data',
action: 'play',
type: 'relation',
action: ['play'],
entity: 'user',
checker: (data) => {
// 不记得什么意思了
/* const token = context.getToken();
const { userId } = token!;
if (userId === operation.filter!.id) {
throw new OakRowInconsistencyException();
} */
relationFilter: (operation, context) => {
if (!context.isReallyRoot()) {
// 只有root才能进行操作
throw new OakUserUnpermittedException();
}
return undefined;
},
errMsg: '越权操作'
},
{
type: 'data',

View File

@ -1,6 +1,7 @@
{
"component": true,
"usingComponents": {
"oak-icon": "../../icon/index",
"l-button": "../../../miniprogram_npm/lin-ui/button/index",
"l-icon": "../../../miniprogram_npm/lin-ui/icon/index",
"l-grid": "../../../miniprogram_npm/lin-ui/grid/index",

View File

@ -1,24 +1,10 @@
@import "../../../config/styles/mp/index.less";
@import "../../../config/styles/mp/mixins.less";
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
}
.grid-item {
min-width: 25%;
padding: 20rpx;
display: flex;
flex-direction: column;
justify-content: center;
box-sizing: border-box;
}
.block {
width: 176rpx;
height: 176rpx;
background: #fff;
background: transparent;
color: #333;
display: flex;
}
@ -36,20 +22,9 @@
flex-direction: column;
padding: 32rpx;
}
.five-grid {
position: unset;
}
.external-class-content {
padding: 32rpx 0 !important;
}
.image-icon {
width: 96rpx !important;
height: 96rpx !important;
}
.image {
width: 100%;
height: 100%;
.label {
margin-top: 16rpx;
font-size: xx-small;
color: @oak-color-primary;
}

View File

@ -3,15 +3,13 @@ export default OakComponent({
properties: {
actions: Array,
actionDescriptions: Object,
show: {
type: Boolean,
value: false,
},
iconSize: String,
},
methods: {
onClick(action: string) {
const { onActionClick } = this.props;
onActionClick(action);
onClickMp(e: WechatMiniprogram.TouchEvent) {
const { index } = e.detail;
const action = this.props.actions[index];
this.triggerEvent('action', { action });
},
},

View File

@ -1,10 +1,10 @@
<view class="block block--bottom">
<view class="btn-box">
<l-grid row-num="{{5}}">
<l-grid row-num="{{4}}">
<block wx:for="{{actionss}}" wx:key="index">
<l-grid-item bind:lintap="onClick" data-index="{{index}}" key="{{index}}" slot="{{index}}">
<l-icon class="image" name="{{item.icon.name}}" slot="image" />
<text>{{item.text}}</text>
<l-grid-item bind:linitemtap="onClickMp" data-index="{{index}}" key="{{index}}" slot="{{index}}">
<oak-icon class="image" name="{{item.icon.name}}" type="{{item.icon.type || 'fas'}}" size="{{iconSize || 66}}" slot="image" />
<text class='label'>{{item.label}}</text>
</l-grid-item>
</block>
</l-grid>

View File

@ -7,7 +7,6 @@ export default OakComponent({
},
methods: {
printDebugStore(e: any) {
console.log(e);
console.log(this.features.cache.getFullData());
},
printCachedStore() {

View File

@ -14,6 +14,7 @@ export class BackendRuntimeContext<ED extends EntityDict> extends AsyncContext<E
private application?: Partial<ED['application']['Schema']>;
private token?: Partial<ED['token']['Schema']>;
private amIRoot?: boolean;
private amIReallyRoot?: boolean;
private rootMode?: boolean;
protected async initialize(data?: SerializedData) {
@ -65,6 +66,22 @@ export class BackendRuntimeContext<ED extends EntityDict> extends AsyncContext<E
id: 1,
userId: 1,
playerId: 1,
player: {
id: 1,
userState: 1,
userRole$user: {
$entity: 'userRole',
data: {
id: 1,
userId: 1,
roleId: 1,
role: {
id: 1,
name: 1,
}
},
},
},
ableState: 1,
user: {
id: 1,
@ -98,14 +115,15 @@ export class BackendRuntimeContext<ED extends EntityDict> extends AsyncContext<E
if (token.ableState === 'disabled') {
throw new OakTokenExpiredException();
}
const { user } = token;
const { userState, userRole$user } = user!;
/* if (['disabled', 'merged'].includes(userState as string)) {
throw new OakUserDisabledException();
} */
const { user, player } = token;
const { userRole$user } = user!;
this.amIRoot = (userRole$user as any).length > 0 && (userRole$user as any).find(
(ele: any) => ele.role.name === 'root'
);
const { userRole$user: userRole$player} = player!;
this.amIReallyRoot = (userRole$player as any).length > 0 && (userRole$player as any).find(
(ele: any) => ele.role.name === 'root'
);
this.token = token;
}
await this.commit();
@ -178,6 +196,10 @@ export class BackendRuntimeContext<ED extends EntityDict> extends AsyncContext<E
return !!this.amIRoot;
}
isReallyRoot(): boolean {
return !!this.amIReallyRoot;
}
sendMessage(data: ED['message']['CreateSingle']['data']) {
return this.operate('message', {
action: 'create',
@ -186,4 +208,22 @@ export class BackendRuntimeContext<ED extends EntityDict> extends AsyncContext<E
dontCollect: true,
});
}
allowUserUpdate(): boolean {
const userInfo = this.token?.user;
if (userInfo) {
const { userState } = userInfo;
if (userState === 'disabled') {
throw new OakUserDisabledException('您的帐号已经被禁用,请联系客服');
}
else if (['shadow', 'merged'].includes(userState!)) {
throw new OakTokenExpiredException('您的登录状态有异常,请重新登录 ');
}
else {
assert(userState === 'normal');
}
return true;
}
throw new OakUnloggedInException('您尚未登录');
}
}

View File

@ -9,6 +9,9 @@ import { CommonAspectDict } from 'oak-common-aspect';
import { SyncContext, SyncRowStore } from 'oak-domain/lib/store/SyncRowStore';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import { BackendRuntimeContext } from './BackendRuntimeContext';
import { OakTokenExpiredException, OakUserDisabledException } from '../types/Exception';
import { assert } from 'console';
import { OakUnloggedInException } from 'oak-domain/lib/types';
type AspectDict<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>> = GeneralAspectDict<ED, Cxt> & CommonAspectDict<ED, Cxt>;
// 上下文被serialize后的数据内容
@ -76,4 +79,26 @@ export class FrontendRuntimeContext<
isRoot() {
return this.token?.isRoot() || false;
}
isReallyRoot(): boolean {
return this.token?.isReallyRoot() || false;
}
allowUserUpdate(): boolean {
const userInfo = this.token?.getUserInfo();
if (userInfo) {
const { userState } = userInfo;
if (userState === 'disabled') {
throw new OakUserDisabledException('您的帐号已经被禁用,请联系客服');
}
else if (['shadow', 'merged'].includes(userState!)) {
throw new OakTokenExpiredException('您的登录状态有异常,请重新登录 ');
}
else {
assert(userState === 'normal');
}
return true;
}
throw new OakUnloggedInException('您尚未登录');
}
};

View File

@ -13,4 +13,6 @@ export interface RuntimeContext {
getTokenValue(allowUnloggedIn?: boolean): string | undefined;
isRoot(): boolean;
isReallyRoot(): boolean;
};

View File

@ -45,6 +45,14 @@ const userProjection: EntityDict['user']['Selection']['data'] = {
userId: 1,
},
},
userRole$user: {
$entity: 'userRole',
data: {
id: 1,
userId: 1,
roleId: 1,
},
},
};
const tokenProjection: EntityDict['token']['Selection']['data'] = {
id: 1,
@ -196,6 +204,20 @@ export class Token<
}
isRoot(): boolean {
const token = this.getToken(true);
const userRole$user = token?.user?.userRole$user;
return !!(
userRole$user &&
userRole$user?.length > 0 &&
userRole$user.find((ele) => ele.roleId === ROOT_ROLE_ID)
);
}
/**
* token的player到底是不是root
* @returns
*/
isReallyRoot(): boolean {
const token = this.getToken(true);
const userRole$user = token?.player?.userRole$user;
return !!(

View File

@ -15,7 +15,7 @@
</block>
</view>
<view class="cell">
<l-list title="手机号" icon="phone" desc="{{mobileText}}" bind:lintap="goMyMobile" >
<l-list title="手机号" icon="phone" right-desc="{{mobileText}}" bind:lintap="goMyMobile" >
</l-list>
<block wx:if="{{isRoot}}">
<l-list title="用户管理" icon="user" hover bind:lintap="goUserManage" />

View File

@ -32,6 +32,40 @@
padding: 0rpx 10rpx;
border-bottom: 1px solid #e0e0e0;
background-color: #fff;
.left {
display: flex;
flex-direction: row;
}
.icon {
width: 16rpx;
margin-left: 6rpx;
margin-right: 26rpx;
text-align: center;
}
.label {
color: @oak-text-color-primary;
}
.value {
color: @oak-text-color-secondary;
margin-right: 16rpx;
.primary {
background-color: @oak-color-primary;
}
.success {
background-color: @oak-color-success;
}
.danger {
background-color: @oak-color-error;
}
.warning {
background-color: @oak-color-warning;
}
}
}

View File

@ -100,31 +100,34 @@ export default OakComponent({
actionDescriptions: {
accept: {
icon: {
name: 'pan_tool',
name: 'circle-check',
type: 'far',
},
label: '通过',
},
activate: {
icon: {
name: 'check',
name: 'chart-line',
},
label: '激活',
},
disable: {
icon: {
name: 'flash_off',
name: 'bell-slash',
type: 'far',
},
label: '禁用',
},
enable: {
icon: {
name: 'flash_on',
name: 'bell',
type: 'far',
},
label: '启用',
},
remove: {
icon: {
name: 'clear',
name: 'trash',
},
label: '删除',
},
@ -136,19 +139,19 @@ export default OakComponent({
},
verify: {
icon: {
name: 'how_to_reg',
name: 'certificate',
},
label: '验证',
},
play: {
icon: {
name: 'play_circle',
name: 'person-praying',
},
label: '切换',
},
},
},
methods: {
methods: {
async onActionClick(action: string) {
switch (action) {
case 'update': {
@ -175,5 +178,9 @@ export default OakComponent({
this.navigateBack(2);
}
},
onActionClickMp(e: WechatMiniprogram.TouchEvent) {
const { action } = e.detail;
return this.onActionClick(action);
}
},
});

View File

@ -9,37 +9,81 @@
</block>
</view>
<view class="col">
<l-list right-desc="{{nickname || '未设置'}}" is-link="{{false}}">
<view slot="left-section">
<oak-icon name="user" />
昵称
<l-list is-link="{{false}}">
<view slot="left-section" class="left">
<view class="icon">
<oak-icon name="comment-dots" />
</view>
<view class="label">
昵称
</view>
</view>
<view slot="right-section" class="value">{{nickname || '未设置'}}</view>
</l-list>
<l-list is-link="{{false}}">
<view slot="left-section" class="left">
<view class="icon">
<oak-icon name="file-signature" />
</view>
<view class="label">
姓名
</view>
</view>
<view slot="right-section" class="value">{{name || '未设置'}}</view>
</l-list>
<l-list is-link="{{false}}">
<view slot="left-section" class="left">
<view class="icon">
<oak-icon name="person-half-dress" />
</view>
<view class="label">
性别
</view>
</view>
<view slot="right-section" class="value">{{gender || '未设置'}}</view>
</l-list>
<l-list is-link="{{false}}">
<view slot="left-section" class="left">
<view class="icon">
<oak-icon name="mobile-screen" />
</view>
<view class="label">
手机号
</view>
</view>
<view slot="right-section" class="value">{{mobileText}}</view>
</l-list>
<l-list tag-position="right" is-link="{{false}}">
<view slot="left-section" class="left">
<view class="icon">
<oak-icon name="info" />
</view>
<view class="label">
用户状态
</view>
</view>
<view slot="right-section" class="value">
<l-tag l-class="{{stateColor[userState]}}" size="mini" shape="circle">
{{userState || '未设置'}}
</l-tag>
</view>
</l-list>
<l-list right-desc="{{name || '未设置'}}" is-link="{{false}}">
<view slot="left-section">
<oak-icon name="signature" />
姓名
<l-list tag-position="right" is-link="{{false}}">
<view slot="left-section" class="left">
<view class="icon">
<oak-icon name="chess-bishop" />
</view>
<view class="label">
认证状态
</view>
</view>
</l-list>
<l-list right-desc="{{mobileText}}" is-link="{{false}}">
<view slot="left-section">
<oak-icon name="mobile" />
手机号
</view>
</l-list>
<l-list tag-position="right" tag-content="{{userState}}" tag-plain="{{true}}" is-link="{{false}}">
<view slot="left-section">
<oak-icon name="artstation" />
用户状态
</view>
</l-list>
<l-list tag-position="right" tag-content="{{idState}}" tag-plain="{{true}}" is-link="{{false}}">
<view slot="left-section">
<oak-icon name="chess-bishop" />
认证状态
<view slot="right-section" class="value">
<l-tag l-class="{{idStateColor[idState]}}" size="mini" shape="circle">
{{idState || '未设置'}}
</l-tag>
</view>
</l-list>
</view>
<view style="flex:1" />
<actionPanel actions="{{oakLegalActions}}" actionDescriptions="{{actionDescriptions}}" bind:click="onActionClick" />
<actionPanel actions="{{oakLegalActions}}" actionDescriptions="{{actionDescriptions}}" bind:action="onActionClickMp" />
</view>

View File

@ -60,10 +60,10 @@
font-size: small;
.label {
min-width: 140rpx;
color: @oak-text-color-secondary;
color: @oak-text-color-primary;
}
.value {
color: @oak-text-color-primary;
color: @oak-text-color-secondary;
margin-left: 30rpx;
}
}

View File

@ -1,12 +1,9 @@
{
"navigationBarTitleText": "修改用户信息",
"usingComponents": {
"t-button": "../../../../miniprogram_npm/tdesign/button/button",
"t-picker": "../../../../miniprogram_npm/tdesign/picker/picker",
"t-picker-item": "../../../../miniprogram_npm/tdesign/picker/picker-item",
"t-date-time-picker": "../../../../miniprogram_npm/tdesign/date-time-picker/date-time-picker",
"t-cell": "../../../../miniprogram_npm/tdesign/cell/cell",
"t-input": "../../../../miniprogram_npm/tdesign/input/input",
"t-icon": "../../../../miniprogram_npm/tdesign/icon/icon"
"l-button": "../../../../miniprogram_npm/lin-ui/button/index",
"l-list": "../../../../miniprogram_npm/lin-ui/list/index",
"l-input": "../../../../miniprogram_npm/lin-ui/input/index",
"l-icon": "../../../../miniprogram_npm/lin-ui/icon/index"
}
}

View File

@ -16,22 +16,8 @@
display: flex;
flex-direction: column;
background-color: #fff;
}
.pannel-item {
display: flex;
align-items: center;
height: 96rpx;
padding: 0 32rpx;
font-size: 32rpx;
background-color: #fff;
}
.pannel-label {
width: 160rpx;
margin-right: 32rpx;
.label {
color: @oak-text-color-primary;
}
}
.pannel-text {
flex: 1;
opacity: 0.9;
}

View File

@ -1,4 +1,5 @@
import {DateTime} from 'luxon';
import { EntityDict } from "../../../../general-app-domain";
const GenderOptions = [
{
value: 'male', label: '男',
@ -20,12 +21,6 @@ const IDCardTypeOptions = [
}
];
const PICKER_KEY = {
SEX: 'sex',
IDCARD: 'idCard',
BIRTH: 'birth'
};
export default OakComponent({
entity: 'user',
projection: {
@ -41,79 +36,78 @@ export default OakComponent({
isList: false,
formData: ({ data: user }) => {
const { birth, gender, idCardType } = user || {};
const birthText = birth && new Date(birth).toLocaleDateString();
const birthDate = birth && new Date(birth);
const birthText = birthDate && birthDate.toLocaleDateString();
const birthDayValue = birthDate && `${birthDate.getFullYear()}-${birthDate.getMonth() + 1}-${birthDate.getDate()}`;
const GenderDict = {
male: '男',
female: '女',
};
const genderText = gender && GenderDict[gender];
const genderIndex =
gender && GenderOptions.find((ele) => ele.value === gender);
const IdCardTypeDict = {
'ID-Card': '身份证',
passport: '护照',
'Mainland-passport': '港澳通行证',
};
const idCardTypeText = idCardType && IdCardTypeDict[idCardType];
const genderOption = gender && GenderOptions.find(
ele => ele.value === gender
);
const genderText = genderOption && genderOption.label;
const genderOptionIndex = genderOption && GenderOptions.indexOf(genderOption);
const idCardTypeOption = idCardType && IDCardTypeOptions.find(
ele => ele.value === idCardType
);
const idCardTypeText = idCardTypeOption && idCardTypeOption.label;
const idCardTypeOptionIndex = idCardTypeOption && IDCardTypeOptions.indexOf(idCardTypeOption);
const idCardTypeIndex =
idCardType && IDCardTypeOptions.find((ele) => ele.value === gender);
const now = new Date();
return Object.assign({}, user, {
birthText,
birthDayValue,
genderText,
idCardTypeText,
idCardTypeOptionIndex,
oldestBirthday: `${now.getFullYear() - 120}-01-01`,
today: `${now.getFullYear()}-${
now.getMonth() + 1
}-${now.getDate()}`,
genderIndex,
genderOptionIndex,
idCardTypeIndex,
});
},
data: {
birthEnd: '', // for小程序的picker
GenderOptions,
IDCardTypeOptions,
PICKER_KEY,
birthVisible: false,
},
lifetimes: {
ready() {
const today = new Date();
const birthEnd = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`;
this.setState({ birthEnd });
}
},
methods: {
setValue(input: any) {
console.log(input, 123);
const { dataset, value } = this.resolveInput(input);
const { key } = dataset;
if (['sex', 'idCard'].includes(key)) {
this.update({[dataset!.attr]: value[0]});
return;
}
if (key === 'birth') {
this.update({[dataset!.attr]: value});
this.setState({
birthVisible: false,
});
return;
}
this.update({[dataset!.attr]: value[0]});
setValueMp(input: WechatMiniprogram.Input) {
const { detail, target: { dataset }} = input;
const { attr } = dataset!;
const { value } = detail;
this.update({[attr]: value});
},
async confirm() {
async confirmMp() {
await this.execute();
if (this.props.oakFrom === 'user:manage:list') {
this.navigateBack();
} else if (this.props.oakFrom === 'user:manage:detail') {
this.navigateBack();
}
},
onClickPicker(e: any) {
const { key } = e?.currentTarget?.dataset;
this.setState({
[`${key}Visible`]: true,
});
},
onPickerClose(e: any) {
const { key } = e?.currentTarget?.dataset;
this.setState({
[`${key}Visible`]: false,
});
this.navigateBack();
},
onBirthChange(e: WechatMiniprogram.Input) {
const { detail: { value }} = e;
const birth = new Date(value);
this.update({ birth });
},
onIdCardTypeChange(e: any) {
const { detail: { value: index }} = e;
const { value } = IDCardTypeOptions[index];
this.update({ idCardType: value as EntityDict['user']['OpSchema']['idCardType'] });
},
onGenderChange(e: any) {
const { detail: { value: index }} = e;
const { value } = GenderOptions[index];
this.update({ gender: value as EntityDict['user']['OpSchema']['gender'] });
},
},
});

View File

@ -1,50 +1,27 @@
<!-- index.wxml -->
<view class="page-body">
<view class="col">
<t-input placeholder="请输入" label="slot" size="medium" confirm-type="next" oak:value="nickname" bind:change="setValue">
<text slot="label">
<text style="color: #e34d59">*</text>昵称
</text>
</t-input>
<t-input placeholder="请输入" label="slot" size="medium" confirm-type="next" oak:value="name" bind:change="setValue">
<text slot="label">
<text style="color: #e34d59">*</text>姓名
</text>
</t-input>
<view class="pannel-item" bind:tap="onClickPicker" data-key="{{PICKER_KEY.SEX}}">
<view class="pannel-label">
<text style="color: #e34d59">*</text>性别
</view>
<view class="pannel-text">{{genderText || '选择性别'}}</view>
<t-icon name="chevron-right" color="rgba(0, 0, 0, 0.26)" size="24px" />
</view>
<view class="pannel-item" bind:tap="onClickPicker" data-key="{{PICKER_KEY.IDCARD}}">
<view class="pannel-label">
<text style="color: #e34d59">*</text>证件类别
</view>
<view class="pannel-text">{{idCardTypeText || '选择证件类别'}}</view>
<t-icon name="chevron-right" color="rgba(0, 0, 0, 0.26)" size="24px" />
</view>
<view class="pannel-item" bind:tap="onClickPicker" data-key="{{PICKER_KEY.BIRTH}}">
<view class="pannel-label">
<text style="color: #e34d59">*</text>出生日期
</view>
<view class="pannel-text">{{birthText || '选择日期'}}</view>
<t-icon name="chevron-right" color="rgba(0, 0, 0, 0.26)" size="24px" />
</view>
<t-picker data-key="{{PICKER_KEY.SEX}}" visible="{{sexVisible}}" oak:value="gender" title="选择性别" cancelBtn="取消" confirmBtn="确认" bindchange="setValue" bindcancel="onPickerClose" bindconfirm="onPickerClose">
<t-picker-item options="{{GenderOptions}}"></t-picker-item>
</t-picker>
<t-picker data-key="{{PICKER_KEY.IDCARD}}" visible="{{idCardVisible}}" oak:value="idCardType" title="选择证件类型" cancelBtn="取消" confirmBtn="确认" bindchange="setValue" bindcancel="onPickerClose" bindconfirm="onPickerClose">
<t-picker-item options="{{IDCardTypeOptions}}"></t-picker-item>
</t-picker>
<t-input placeholder="请输入" label="slot" size="medium" type="number" confirm-type="done" oak:value="idNumber" bind:change="setValue">
<text slot="label">
<text style="color: #e34d59">*</text>证件号
</text>
</t-input>
<t-date-time-picker title="选择日期和时间" data-key="{{PICKER_KEY.BIRTH}}" data-attr="birth" visible="{{birthVisible}}" default-value="{{today}}" mode="date" value="{{birthText}}" format="YYYY-MM-DD" bindchange="setValue" bindcancel="onPickerClose" start="{{oldestBirthday}}" end="{{today}}"></t-date-time-picker>
<l-input placeholder="请输入昵称" label="昵称" value="{{nickname}}" confirm-type="next" bind:lininput="setValueMp" l-label-class="label" data-attr="nickname" />
<l-input placeholder="请输入姓名" label="姓名" value="{{name}}" confirm-type="next" bind:lininput="setValueMp" l-label-class="label" data-attr="name" />
<picker range="{{GenderOptions}}" range-key="label" value="{{genderOptionIndex}}" bind:change="onGenderChange">
<l-input label="性别" value="{{genderText || '选择证件类别'}}" disabled="{{true}}" l-label-class="label" >
<l-icon slot="right" name="right" size="20" />
</l-input>
</picker>
<picker mode="date" end="{{birthEnd}}" value="{{birthDayValue}}" bind:change="onBirthChange">
<l-input label="出生日期" value="{{birthText || '选择日期'}}" disabled="{{true}}" l-label-class="label">
<l-icon slot="right" name="right" size="20" />
</l-input>
</picker>
<picker range="{{IDCardTypeOptions}}" range-key="label" value="{{idCardTypeOptionIndex}}" bind:change="onIdCardTypeChange">
<l-input label="证件类别" value="{{idCardTypeText || '选择证件类别'}}" disabled="{{true}}" l-label-class="label" >
<l-icon slot="right" name="right" size="20" />
</l-input>
</picker>
<l-input placeholder="请输入证件号" label="证件号" value="{{idNumber}}" confirm-type="next" bind:lininput="setValueMp" l-label-class="label" data-attr="idNumber" />
</view>
<view style="flex: 1" />
<t-button theme="primary" disabled="{{oakExecuting || !oakDirty}}" style="margin: 16rpx" loading="{{oakExecuting}}" block size="large" bind:tap="confirm" content="确定" />
<l-button type="default" disabled="{{oakExecuting || !oakDirty}}" loading="{{oakExecuting}}" size="long" bind:lintap="confirmMp">
{{t('common:action.confirm')}}
</l-button>
</view>

View File

@ -9,6 +9,7 @@ import { ROOT_ROLE_ID, ROOT_USER_ID } from '../constants';
import { addFilterSegment } from 'oak-domain/lib/store/filter';
import { randomName } from '../utils/randomUser';
import { RuntimeCxt } from '../types/RuntimeCxt';
import { OakRowInconsistencyException } from 'oak-domain/lib/types';
let NO_ANY_USER = true;
const triggers: Trigger<EntityDict, 'user', RuntimeCxt>[] = [
@ -105,10 +106,14 @@ const triggers: Trigger<EntityDict, 'user', RuntimeCxt>[] = [
entity: 'user',
action: 'play',
when: 'after',
fn: async ({ operation }, context) => {
fn: async ({ operation }, context, option) => {
const { filter } = operation;
assert(filter!.id);
const { id } = context.getToken()!;
const token = context.getToken()!;
const { id, userId } = token;
if (userId === filter!.id) {
throw new OakRowInconsistencyException(undefined, '您已经是当前用户');
}
await context.operate('token', {
id: generateNewId(),
action: 'update',
@ -118,9 +123,7 @@ const triggers: Trigger<EntityDict, 'user', RuntimeCxt>[] = [
filter: {
id,
}
}, {
dontCollect: true,
});
}, option);
return 1;
}
} as UpdateTrigger<EntityDict, 'user', RuntimeCxt>,