增加 tsconfig.lib,json 及user、token、mobile移到components下

This commit is contained in:
Wang Kejun 2023-11-08 18:35:11 +08:00
parent fa89f269ce
commit 83559d4b17
195 changed files with 6763 additions and 564 deletions

View File

@ -13,7 +13,7 @@
z-index: 999;
width: 750rpx;
height: calc(106rpx + env(safe-area-inset-bottom));
height: 106rpx;
display: flex;
background-color: white;
background-size: 100% 100%;

8
es/components/mobile/login/index.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, keyof import("../../../oak-app-domain").EntityDict, false, {
onlyCaptcha: boolean;
onlyPassword: boolean;
eventLoggedIn: string;
callback: (() => void) | undefined;
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,119 @@
import { LOCAL_STORAGE_KEYS } from '../../../config/constants';
const SEND_KEY = LOCAL_STORAGE_KEYS.captchaSendAt;
const SEND_CAPTCHA_LATENCY = process.env.NODE_ENV === 'development' ? 10 : 60;
export default OakComponent({
isList: false,
projection: {
id: 1,
mobile: 1,
userId: 1,
},
data: {
mobile: '',
captcha: '',
counter: 0,
refreshing: false,
password: '',
},
properties: {
onlyCaptcha: false,
onlyPassword: false,
eventLoggedIn: '',
callback: undefined,
},
formData({ features }) {
const lastSendAt = features.localStorage.load(SEND_KEY);
const now = Date.now();
let counter = 0;
if (typeof lastSendAt === 'number') {
counter = Math.max(SEND_CAPTCHA_LATENCY - Math.ceil((now - lastSendAt) / 1000), 0);
if (counter > 0) {
this.counterHandler = setTimeout(() => this.reRender(), 1000);
}
else if (this.counterHandler) {
clearTimeout(this.counterHandler);
this.counterHandler = undefined;
}
}
return {
counter,
};
},
methods: {
setMobile(value) {
this.setState({
mobile: value,
});
},
setCaptcha(value) {
this.setState({
captcha: value,
});
},
async sendCaptcha() {
const { mobile } = this.state;
try {
const result = await this.features.token.sendCaptcha(mobile, 'login');
// 显示返回消息
this.setMessage({
type: 'success',
content: result,
});
this.save(SEND_KEY, Date.now());
this.reRender();
}
catch (err) {
this.setMessage({
type: 'error',
content: err.message,
});
}
},
async loginByMobile() {
const { eventLoggedIn, callback } = this.props;
const { mobile, password, captcha } = this.state;
try {
await this.features.token.loginByMobile(mobile, password, captcha);
if (typeof callback === 'function') {
callback();
}
else if (eventLoggedIn) {
this.pubEvent(eventLoggedIn);
}
else {
this.navigateBack();
}
}
catch (err) {
this.setMessage({
type: 'error',
content: err.message,
});
}
},
async onRefreshMobile(e) {
this.setState({
refreshing: true,
});
try {
const { code, errMsg } = e.detail;
if (errMsg !== 'getPhoneNumber:ok') {
console.error(errMsg);
this.setMessage({
title: '获取手机号失败',
type: 'warning',
});
}
else {
await this.features.token.getWechatMpUserPhoneNumber(code);
}
}
catch (err) {
console.error(err);
}
this.setState({
refreshing: false,
});
},
},
});

20
es/components/mobile/login/web.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function render(props: WebComponentProps<EntityDict, 'token', false, {
counter: number;
loginMode?: number;
loginAgreed?: boolean;
appId: string;
onlyCaptcha?: boolean;
onlyPassword?: boolean;
loading: boolean;
backUrl?: string;
mobile: string;
captcha: string;
password: string;
}, {
setCaptcha: (mobile: string) => void;
setMobile: (mobile: string) => void;
sendCaptcha: () => Promise<void>;
loginByMobile: () => Promise<void>;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,20 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { isMobile, isCaptcha, } from 'oak-domain/lib/utils/validator';
import { MobileOutlined } from '@ant-design/icons';
import { Form, Input, Button } from 'antd';
import Style from './mobile.module.less';
export default function render(props) {
const { mobile, captcha, password, counter } = props.data;
const { t, setMobile, setCaptcha, sendCaptcha, loginByMobile } = props.methods;
const validMobile = isMobile(mobile);
const validCaptcha = isCaptcha(captcha);
const allowSubmit = validMobile && validCaptcha;
const LoginCaptcha = (_jsxs(Form, { colon: true, children: [_jsx(Form.Item, { name: "mobile", children: _jsx(Input, { allowClear: true, value: mobile, "data-attr": "mobile", type: "tel", maxLength: 11, prefix: _jsx(MobileOutlined, {}), placeholder: t('placeholder.Mobile'), size: "large", onChange: (e) => {
setMobile(e.target.value);
}, className: Style['loginbox-input'] }) }), _jsx(Form.Item, { name: "captcha", children: _jsx(Input, { allowClear: true, value: captcha, "data-attr": "captcha",
// type="number"
maxLength: 4, placeholder: t('placeholder.Captcha'), size: "large", onChange: (e) => {
setCaptcha(e.target.value);
}, className: Style['loginbox-input'], suffix: _jsx(Button, { type: "link", disabled: !validMobile || counter > 0, onClick: () => sendCaptcha(), children: counter > 0 ? `${counter}秒后可重发` : t('Send') }) }) }), _jsx(Form.Item, { children: _jsx(Button, { block: true, size: "large", type: "primary", htmlType: "submit", disabled: !allowSubmit, onClick: () => loginByMobile(), children: t('Login') }) })] }));
return (_jsx("div", { className: Style['loginbox-main'], children: _jsxs("div", { className: Style['loginbox-wrap'], children: [_jsx("div", { className: Style['loginbox-hd'], children: "\u4E3A\u4E86\u66F4\u597D\u7684\u4F53\u9A8C\uFF0C\u8BF7\u7ED1\u5B9A\u624B\u673A\u53F7" }), _jsx("div", { className: Style['loginbox-bd'], children: _jsx("div", { className: Style['loginbox-mobile'], children: LoginCaptcha }) })] }) }));
}

19
es/components/mobile/login/web.pc.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function render(props: WebComponentProps<EntityDict, 'token', false, {
counter: number;
loginMode?: number;
loginAgreed?: boolean;
appId: string;
onlyCaptcha?: boolean;
onlyPassword?: boolean;
loading: boolean;
backUrl?: string;
mobile: string;
captcha: string;
}, {
setCaptcha: (mobile: string) => void;
setMobile: (mobile: string) => void;
sendCaptcha: () => Promise<void>;
loginByMobile: () => Promise<void>;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,20 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { isMobile, isCaptcha, } from 'oak-domain/lib/utils/validator';
import { MobileOutlined } from '@ant-design/icons';
import { Form, Input, Button } from 'antd';
import Style from './web.module.less';
export default function render(props) {
const { mobile, captcha, counter } = props.data;
const { t, setMobile, setCaptcha, sendCaptcha, loginByMobile } = props.methods;
const validMobile = isMobile(mobile);
const validCaptcha = isCaptcha(captcha);
const allowSubmit = validMobile && validCaptcha;
const LoginCaptcha = (_jsxs(Form, { colon: true, children: [_jsx(Form.Item, { name: "mobile", children: _jsx(Input, { allowClear: true, value: mobile, "data-attr": "mobile", type: "tel", maxLength: 11, prefix: _jsx(MobileOutlined, {}), placeholder: t('placeholder.Mobile'), size: "large", onChange: (e) => {
setMobile(e.target.value);
}, className: Style['loginbox-input'] }) }), _jsx(Form.Item, { name: "captcha", children: _jsx(Input, { allowClear: true, value: captcha, "data-attr": "captcha",
// type="number"
maxLength: 4, placeholder: t('placeholder.Captcha'), size: "large", onChange: (e) => {
setCaptcha(e.target.value);
}, className: Style['loginbox-input'], suffix: _jsx(Button, { type: "link", disabled: !validMobile || counter > 0, onClick: () => sendCaptcha(), children: counter > 0 ? `${counter}秒后可重发` : t('Send') }) }) }), _jsx(Form.Item, { children: _jsx(Button, { block: true, size: "large", type: "primary", htmlType: "submit", disabled: !allowSubmit, onClick: () => loginByMobile(), children: t('Login') }) })] }));
return (_jsx("div", { className: Style['loginbox-main'], children: _jsxs("div", { className: Style['loginbox-wrap'], children: [_jsx("div", { className: Style['loginbox-hd'], children: "\u4E3A\u4E86\u66F4\u597D\u7684\u4F53\u9A8C\uFF0C\u8BF7\u7ED1\u5B9A\u624B\u673A\u53F7" }), _jsx("div", { className: Style['loginbox-bd'], children: _jsx("div", { className: Style['loginbox-mobile'], children: LoginCaptcha }) })] }) }));
}

5
es/components/mobile/me/index.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "mobile", true, {
showBack: boolean;
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,96 @@
export default OakComponent({
entity: 'mobile',
isList: true,
projection: {
id: 1,
mobile: 1,
userId: 1,
ableState: 1,
},
filters: [
{
filter() {
// const token = this.features.token.getToken();
const userId = this.features.token.getUserId();
return {
userId,
};
},
},
],
formData: ({ data: mobiles, features }) => {
const token = features.token.getToken();
const tokenMobileId = token.entity === 'mobile' && token.entityId;
return {
tokenMobileId,
mobiles,
allowRemove: mobiles.length > 1,
};
},
data: {
confirmDeleteModalVisible: false,
refreshing: false,
deleteIdx: undefined,
},
properties: {
showBack: false,
},
methods: {
async onRefreshMobile(e) {
this.setState({
refreshing: true,
});
try {
const { code, errMsg } = e.detail;
if (errMsg !== 'getPhoneNumber:ok') {
console.error(errMsg);
this.setMessage({
title: '获取手机号失败',
type: 'warning',
});
}
else {
await this.features.token.getWechatMpUserPhoneNumber(code);
}
}
catch (err) {
console.error(err);
}
this.setState({
refreshing: false,
});
},
goAddMobile() {
const eventLoggedIn = `mobile:me:login:${Date.now()}`;
this.subEvent(eventLoggedIn, () => {
this.navigateBack();
});
this.navigateTo({
url: '/mobile/login',
eventLoggedIn,
});
},
async onRemoveConfirm() {
const { mobileId } = this.state;
this.removeItem(mobileId);
await this.execute();
this.setState({
confirmDeleteModalVisible: false,
mobileId: '',
});
},
onRemoveModalOpen(e) {
const mobileId = e.currentTarget.dataset.id;
this.setState({
confirmDeleteModalVisible: true,
mobileId,
});
},
onRemoveModalClose() {
this.setState({
confirmDeleteModalVisible: false,
mobileId: '',
});
},
},
});

9
es/components/mobile/me/web.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function render(props: WebComponentProps<EntityDict, 'mobile', true, {
mobiles?: EntityDict['mobile']['OpSchema'][];
allowRemove: boolean;
tokenMobileId?: string;
}, {
goAddMobile: () => void;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,23 @@
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { List, Button, Dialog } from 'antd-mobile';
import { MobileOutlined, DeleteOutlined } from '@ant-design/icons';
import Style from './mobile.module.less';
export default function render(props) {
const { mobiles, allowRemove, tokenMobileId } = props.data;
const { goAddMobile, removeItem, recoverItem, execute } = props.methods;
return (_jsxs("div", { className: Style.container, children: [mobiles && mobiles.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(List, { className: Style.list, children: mobiles?.map((ele, index) => (_jsx(List.Item, { prefix: _jsx(MobileOutlined, {}), extra: allowRemove && tokenMobileId !== ele.id && (_jsx("div", { onClick: async () => {
const result = await Dialog.confirm({
content: '确认删除吗?删除后无法用此号码登录',
});
if (result) {
removeItem(ele.id);
try {
await execute();
}
catch (err) {
recoverItem(ele.id);
throw err;
}
}
}, children: _jsx(DeleteOutlined, {}) })), children: ele.mobile }, index))) }), _jsx("div", { style: { flex: 1 } })] })) : (_jsx("div", { className: Style.noData, children: _jsx("span", { children: "\u5C1A\u672A\u7ED1\u5B9A\u624B\u673A\u53F7" }) })), _jsx(Button, { block: true, size: "large", color: "primary", onClick: () => goAddMobile(), children: "\u7ED1\u5B9A" })] }));
}

10
es/components/mobile/me/web.pc.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function render(props: WebComponentProps<EntityDict, 'mobile', true, {
mobiles?: EntityDict['mobile']['OpSchema'][];
allowRemove: boolean;
showBack: boolean;
tokenMobileId?: string;
}, {
goAddMobile: () => void;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,39 @@
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useState } from 'react';
import { List, Button, Modal, Row, Col } from 'antd';
import { MobileOutlined, DeleteOutlined } from '@ant-design/icons';
import MobileLogin from '../login';
export default function render(props) {
const { mobiles, allowRemove, tokenMobileId, showBack = false } = props.data;
const { goAddMobile, removeItem, recoverItem, execute, subEvent } = props.methods;
const [open, setOpen] = useState(false);
const eventLoggedIn = `user:info:login:${Date.now()}`;
return (_jsxs(_Fragment, { children: [_jsx(Button, { type: "primary", onClick: () => {
setOpen(true);
}, style: { marginBottom: 16 }, children: "\u7ED1\u5B9A" }), _jsx(Row, { children: _jsx(Col, { xs: 24, sm: 12, children: _jsx(List, { bordered: true, children: mobiles?.map((ele, index) => (_jsx(List.Item, { extra: allowRemove &&
tokenMobileId !== ele.id && (_jsx("div", { onClick: () => {
const modal = Modal.confirm({
title: `确认删除吗?删除后无法用此号码登录`,
okText: '确定',
cancelText: '取消',
onOk: async (e) => {
removeItem(ele.id);
try {
await execute();
}
catch (err) {
recoverItem(ele.id);
throw err;
}
modal.destroy();
},
onCancel: (e) => {
modal.destroy();
},
});
}, children: _jsx(DeleteOutlined, {}) })), children: _jsx(List.Item.Meta, { avatar: _jsx(MobileOutlined, {}), title: ele.mobile }) }, index))) }) }) }), _jsx(Modal, { title: "\u7ED1\u5B9A\u624B\u673A\u53F7", open: open, destroyOnClose: true, footer: null, onCancel: () => {
setOpen(false);
}, children: _jsx("div", { style: { padding: 16 }, children: _jsx(MobileLogin, { callback: () => {
setOpen(false);
}, oakPath: "$mobile/me-mobile/login", oakAutoUnmount: true }) }) })] }));
}

4
es/components/token/me/index.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
/// <reference types="wechat-miniprogram" />
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "token", true, WechatMiniprogram.Component.DataOption>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,154 @@
export default OakComponent({
entity: 'token',
isList: true,
projection: {
id: 1,
userId: 1,
playerId: 1,
user: {
id: 1,
nickname: 1,
name: 1,
birth: 1,
gender: 1,
idState: 1,
userState: 1,
isRoot: 1,
extraFile$entity: {
$entity: 'extraFile',
data: {
id: 1,
tag1: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
type: 1,
entity: 1,
entityId: 1,
extension: 1,
},
filter: {
tag1: 'avatar',
},
indexFrom: 0,
count: 1,
},
mobile$user: {
$entity: 'mobile',
data: {
id: 1,
mobile: 1,
},
},
},
player: {
id: 1,
isRoot: 1,
},
},
filters: [
{
filter() {
const tokenId = this.features.token.getTokenValue();
if (tokenId) {
return {
id: tokenId,
};
}
return {
id: 'none',
};
},
},
],
formData: ({ data, features }) => {
const [token] = data || [];
const user = token?.user;
const player = token?.player;
const avatarFile = user?.extraFile$entity && user?.extraFile$entity[0];
const avatar = features.extraFile.getUrl(avatarFile);
const nickname = user && user.nickname;
const mobileData = user && user.mobile$user && user.mobile$user[0];
const { mobile } = mobileData || {};
const mobileCount = user?.mobile$user?.length || 0;
const isLoggedIn = !!token;
const isPlayingAnother = token && token.userId !== token.playerId;
const isRoot = !!player?.isRoot;
const mobileText = mobileCount && mobileCount > 1
? `${mobileCount}条手机号`
: mobile || '未绑定';
return {
tokenId: token?.id,
userId: user?.id,
avatar,
nickname,
mobile,
mobileCount,
mobileText,
isLoggedIn,
isPlayingAnother,
isRoot,
};
},
data: {
refreshing: false,
},
methods: {
async doLogin() {
this.setState({
refreshing: true,
});
try {
switch (process.env.OAK_PLATFORM) {
case 'wechatMp': {
await this.features.token.loginWechatMp();
this.setState({
refreshing: false,
});
break;
}
case 'web': {
const eventLoggedIn = `token:me:login:${Date.now()}`;
this.subEvent(eventLoggedIn, () => {
this.navigateBack();
});
this.navigateTo({
url: '/login',
eventLoggedIn,
}, undefined, true);
break;
}
}
}
catch (err) {
console.error(err);
}
},
goMyMobile() {
this.navigateTo({
url: '/mobile/me',
});
},
goUserManage() {
this.navigateTo({
url: '/user/manage',
});
},
goSetting() {
this.navigateTo({
url: '/setting',
});
},
goMyInfo() {
if (!this.state.isLoggedIn) {
return;
}
this.navigateTo({
url: '/user/info',
oakId: this.state.userId,
});
},
},
});

18
es/components/token/me/web.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
import { EntityDict } from '../../../oak-app-domain';
import { WebComponentProps } from 'oak-frontend-base';
export default function Render(props: WebComponentProps<EntityDict, 'token', true, {
avatar: string;
nickname?: string;
isLoggedIn?: boolean;
mobile?: string;
mobileCount?: number;
refreshing?: boolean;
isRoot: boolean;
tokenId?: string;
mobileText: string;
}, {
goMyInfo: () => Promise<void>;
doLogin: () => Promise<void>;
goMyMobile: () => Promise<void>;
goUserManage: () => Promise<void>;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,9 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { List, Button, Avatar } from 'antd-mobile';
import { UserOutlined, MobileOutlined } from '@ant-design/icons';
import Style from './mobile.module.less';
export default function Render(props) {
const { avatar, isLoggedIn, refreshing, mobileText, isRoot, oakExecuting, tokenId, nickname, oakDirty, } = props.data;
const { doLogin, t, goMyMobile, goUserManage, goMyInfo } = props.methods;
return (_jsxs("div", { className: Style.container, children: [_jsxs("div", { className: Style.userInfo, children: [_jsx(Avatar, { className: Style.avatar, src: avatar }), _jsx("span", { className: Style.nickname, children: nickname || '未设置' }), isLoggedIn ? (_jsx(Button, { color: "primary", size: "small", disabled: refreshing, loading: refreshing, onClick: () => goMyInfo(), children: t('common::action.update') })) : (_jsx(Button, { size: "small", disabled: refreshing, loading: refreshing, onClick: () => doLogin(), children: t('login') }))] }), _jsxs(List, { className: Style.list, children: [_jsx(List.Item, { onClick: () => goMyMobile(), prefix: _jsx(MobileOutlined, {}), title: "\u624B\u673A\u53F7", extra: mobileText }), isRoot && (_jsx(List.Item, { onClick: () => goUserManage(), prefix: _jsx(UserOutlined, {}), title: "\u7528\u6237\u7BA1\u7406" }))] })] }));
}

18
es/components/token/me/web.pc.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
import { EntityDict } from '../../../oak-app-domain';
import { WebComponentProps } from 'oak-frontend-base';
export default function Render(props: WebComponentProps<EntityDict, 'token', true, {
avatar?: string;
nickname?: string;
isLoggedIn?: boolean;
mobile?: string;
mobileCount?: number;
refreshing?: boolean;
isRoot: boolean;
tokenId?: string;
mobileText: string;
}, {
goMyInfo: () => Promise<void>;
doLogin: () => Promise<void>;
goMyMobile: () => Promise<void>;
goUserManage: () => Promise<void>;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,9 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { List, Button, Avatar } from 'antd';
import { UserOutlined, MobileOutlined } from '@ant-design/icons';
import Style from './web.module.less';
export default function Render(props) {
const { avatar, isLoggedIn, refreshing, mobileText, isRoot, oakExecuting, tokenId, nickname, oakDirty, } = props.data;
const { doLogin, t, goMyMobile, goUserManage, goMyInfo } = props.methods;
return (_jsxs("div", { className: Style.container, children: [_jsxs("div", { className: Style.userInfo, children: [avatar ? (_jsx(Avatar, { className: Style.avatar, src: avatar })) : (_jsx(Avatar, { className: Style.avatar, icon: _jsx(UserOutlined, { className: Style.userIcon }) })), _jsx("span", { className: Style.nickname, children: nickname || '未设置' }), isLoggedIn ? (_jsx(Button, { type: "primary", size: "small", disabled: refreshing, loading: refreshing, onClick: () => goMyInfo(), children: t('common::action.update') })) : (_jsx(Button, { size: "small", disabled: refreshing, loading: refreshing, onClick: () => doLogin(), children: t('login') }))] }), _jsxs(List, { className: Style.list, split: true, children: [_jsx(List.Item, { onClick: () => goMyMobile(), children: _jsx(List.Item.Meta, { avatar: _jsx(MobileOutlined, {}), title: "\u624B\u673A\u53F7", description: mobileText }) }), isRoot && (_jsx(List.Item, { onClick: () => goUserManage(), children: _jsx(List.Item.Meta, { avatar: _jsx(UserOutlined, {}), title: "\u7528\u6237\u7BA1\u7406" }) }))] })] }));
}

4
es/components/user/info/index.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
/// <reference types="wechat-miniprogram" />
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "user", false, WechatMiniprogram.Component.DataOption>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,348 @@
import { OakUserInvisibleException } from 'oak-domain/lib/types';
import dayjs from 'dayjs';
import { LOCAL_STORAGE_KEYS } from '../../../config/constants';
const SEND_KEY = LOCAL_STORAGE_KEYS.captchaSendAt;
const SEND_CAPTCHA_LATENCY = process.env.NODE_ENV === 'development' ? 10 : 60;
export default OakComponent({
entity: 'user',
projection: {
id: 1,
name: 1,
nickname: 1,
birth: 1,
gender: 1,
idState: 1,
userState: 1,
mobile$user: {
$entity: 'mobile',
data: {
id: 1,
mobile: 1,
userId: 1,
user: {
id: 1,
userState: 1,
refId: 1,
},
},
},
user$ref: {
$entity: 'user',
data: {
mobile$user: {
$entity: 'mobile',
data: {
id: 1,
mobile: 1,
userId: 1,
user: {
id: 1,
userState: 1,
refId: 1,
},
},
},
},
},
extraFile$entity: {
$entity: 'extraFile',
data: {
id: 1,
tag1: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
extension: 1,
type: 1,
entity: 1,
entityId: 1,
},
filter: {
tag1: 'avatar',
},
indexFrom: 0,
count: 1,
},
wechatUser$user: {
$entity: 'wechatUser',
data: {
id: 1,
openId: 1,
unionId: 1,
userId: 1,
origin: 1,
nickname: 1,
user: {
id: 1,
userState: 1,
refId: 1,
},
},
},
},
isList: false,
formData({ data: user, features }) {
const avatar = user?.extraFile$entity && user?.extraFile$entity[0];
const avatarUrl = features.extraFile.getUrl(avatar);
const { mobile } = (user?.mobile$user && user?.mobile$user[0]) ||
(user?.user$ref &&
user?.user$ref[0] &&
user?.user$ref[0].mobile$user &&
user?.user$ref[0].mobile$user[0]) ||
{};
const genderOption = user?.gender &&
this.state.genderOptions.find((ele) => ele.value === user?.gender);
const lastSendAt = features.localStorage.load(SEND_KEY);
const now = Date.now();
let counter = 0;
if (typeof lastSendAt === 'number') {
counter = Math.max(SEND_CAPTCHA_LATENCY - Math.ceil((now - lastSendAt) / 1000), 0);
if (counter > 0) {
this.counterHandler = setTimeout(() => this.reRender(), 1000);
}
else if (this.counterHandler) {
clearTimeout(this.counterHandler);
this.counterHandler = undefined;
}
}
const isRoot = features.token.isReallyRoot();
return {
id: user?.id,
name: user?.name,
nickname: user?.nickname,
gender: user?.gender,
genderStr: genderOption?.label,
birthText: user?.birth
? dayjs(user.birth).format('YYYY-MM-DD')
: '',
birth: user?.birth,
avatarUrl,
mobile,
userState: user?.userState,
idState: user?.idState,
wechatUser: user?.wechatUser$user?.[0],
counter,
isRoot,
};
},
data: {
stateColor: {
shadow: 'primary',
normal: 'success',
disabled: 'danger',
},
idStateColor: {
verifying: 'primary',
verified: 'success',
unverified: 'warning',
},
genderOptions: [
{
value: 'male',
label: '男',
},
{
value: 'female',
label: '女',
},
],
visible: false,
attr: '',
attrs: {
nickname: '昵称',
gender: '性别',
birth: '出生日期',
},
birthEnd: '',
refreshing: false,
},
lifetimes: {
async ready() {
const { oakId } = this.props;
const userId = this.features.token.getUserId();
if (userId !== oakId) {
throw new OakUserInvisibleException();
}
this.setState({ birthEnd: dayjs().format('YYYY-MM-DD') });
},
},
methods: {
async refreshWechatPublicUserInfo() {
this.setState({
refreshing: true,
});
try {
await this.features.token.refreshWechatPublicUserInfo();
this.setState({
refreshing: false,
});
}
catch (err) {
this.setState({
refreshing: false,
});
throw err;
}
},
goUserManage() {
this.navigateTo({
url: '/user/manage',
});
},
goAddMobile() {
this.navigateTo({
url: '/mobile/me',
}, {
showBack: true,
});
},
goChangePassword() {
this.navigateTo({
url: '/changePassword',
}, {
showBack: true,
});
},
setVisibleMp(e) {
const { target: { dataset }, } = e;
const { attr } = dataset;
this.setVisible(true, attr);
},
genderChangeMp(e) {
const { detail } = e;
const { checked, currentKey } = detail;
const { attr } = this.state;
this.setCustomData(attr, currentKey);
},
birthChangeMp(e) {
const { detail: { value }, } = e;
const birth = dayjs(dayjs(value).format('YYYY-MM-DD')).valueOf();
const { attr } = this.state;
this.setState({
birthText2: dayjs(value).format('YYYY-MM-DD'),
});
this.setCustomData(attr, birth);
},
setVisible(visible, attr) {
this.setState({
visible,
attr: visible ? attr : '',
[`new_${attr}`]: '',
birthText2: '',
});
},
setCustomData(attr, value) {
this.setState({
[`new_${attr}`]: value,
});
},
setCustomDataMp(e) {
const { detail, target: { dataset }, } = e;
const { value } = detail;
const { attr } = this.state;
this.setCustomData(attr, value);
},
updateData(attr, value) {
this.update({
[attr]: this.state[`new_${attr}`],
});
},
async onConfirmMp() {
const { attr } = this.state;
await this.onConfirm(attr);
},
async onConfirm(attr) {
const { oakId } = this.props;
if (!this.state[`new_${attr}`]) {
this.setMessage({
type: 'warning',
content: `${this.state.attrs[attr]}不能为空`,
});
return;
}
this.update({
[attr]: this.state[`new_${attr}`],
});
await this.execute();
this.setVisible(false, attr);
},
onPupopCloseMp() {
const { attr } = this.state;
this.clean();
this.setVisible(false, attr);
},
async updateMyInfo() {
if (!this.state.name) {
this.setMessage({
type: 'error',
content: '请输入姓名',
});
return;
}
if (!this.state.nickname) {
this.setMessage({
type: 'error',
content: '请输入昵称',
});
return;
}
// if (!this.state.gender) {
// this.setMessage({
// type: 'error',
// content: '请选择性别',
// });
// return;
// }
// if (!this.state.birth) {
// this.setMessage({
// type: 'error',
// content: '请选择生日',
// });
// return;
// }
await this.execute('update');
},
async sendCaptcha() {
const { mobile } = this.state;
try {
const result = await this.features.token.sendCaptcha(mobile, 'login');
// 显示返回消息
this.setMessage({
type: 'success',
content: result,
});
this.save(SEND_KEY, Date.now());
this.reRender();
}
catch (err) {
this.setMessage({
type: 'error',
content: err.message,
});
}
},
async unbindingWechat(captcha) {
const { mobile, wechatUser } = this.state;
try {
await this.features.cache.exec('unbindingWechat', {
wechatUserId: wechatUser.id,
mobile,
captcha,
});
this.refresh();
this.setMessage({
content: '解绑成功',
type: 'success',
});
}
catch (err) {
this.setMessage({
content: '解绑失败',
type: 'warning',
});
}
},
},
});

View File

@ -0,0 +1,17 @@
{
"navigationBarTitleText": "个人设置",
"enablePullDownRefresh": false,
"usingComponents": {
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-avatar": "@oak-frontend-base/miniprogram_npm/lin-ui/avatar/index",
"l-list": "@oak-frontend-base/miniprogram_npm/lin-ui/list/index",
"l-icon": "@oak-frontend-base/miniprogram_npm/lin-ui/icon/index",
"l-tag": "@oak-frontend-base/miniprogram_npm/lin-ui/tag/index",
"l-popup": "@oak-frontend-base/miniprogram_npm/lin-ui/popup/index",
"l-input": "@oak-frontend-base/miniprogram_npm/lin-ui/input/index",
"l-radio-group": "@oak-frontend-base/miniprogram_npm/lin-ui/radio-group/index",
"l-radio": "@oak-frontend-base/miniprogram_npm/lin-ui/radio/index",
"l-form-item": "@oak-frontend-base/miniprogram_npm/lin-ui/form-item/index",
"oak-extraFile-avatar": "../../../components/extraFile/avatar/index"
}
}

View File

@ -0,0 +1,90 @@
/** index.wxss **/
@import "../../../config/styles/mp/index.less";
@import "../../../config/styles/mp/mixins.less";
.page-body {
height: 100vh;
display: flex;
flex: 1;
flex-direction: column;
background-color: @oak-bg-color-page;
box-sizing: border-box;
.safe-area-inset-bottom();
}
.avatar {
margin: 10rpx 0;
}
.col {
margin-top: 20rpx;
display: flex;
flex-direction: column;
padding: 0rpx 10rpx;
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;
}
}
}
.pupop-content {
display: flex;
flex-direction: column;
background-color: #fff;
min-height: 300rpx;
.safe-area-inset-bottom();
.pupop-header {
padding: 20rpx;
display: flex;
flex-direction: column;
align-items: flex-end;
.close {
display: block;
}
}
.pupop-form {
display: flex;
flex-direction: column;
flex: 1;
}
}

View File

@ -0,0 +1,82 @@
<view class="page-body">
<view class="col">
<l-list title="头像">
<view slot="right-section" class="avatar">
<oak-extraFile-avatar oakAutoUnmount="{{true}}" oakPath="{{ oakFullpath ? oakFullpath + '.extraFile$entity' : undefined }}" entity="user" entityId="{{id}}" autoUpload="{{true}}"/>
</view>
</l-list>
<l-list title="昵称" data-attr="nickname" bind:lintap="setVisibleMp">
<view slot="right-section" class="value">{{nickname || '未设置'}}</view>
</l-list>
<l-list title="姓名" data-attr="name" bind:lintap="setVisibleMp">
<view slot="right-section" class="value">{{name || '未设置'}}</view>
</l-list>
<l-list title="性别" data-attr="gender" bind:lintap="setVisibleMp">
<view slot="right-section" class="value">{{genderStr || '未设置'}}</view>
</l-list>
<l-list title="生日" data-attr="birth" bind:lintap="setVisibleMp">
<view slot="right-section" class="value">{{birthText || '未设置'}}</view>
</l-list>
<l-list title="手机号" bind:lintap="goAddMobile">
<view slot="right-section" class="value">{{mobile || '未绑定'}}</view>
</l-list>
<!-- <l-list tag-position="right" is-link="{{false}}" title="用户状态">
<view slot="right-section" class="value">
<l-tag l-class="{{stateColor[userState]}}" size="mini" shape="circle">
{{userState || '未设置'}}
</l-tag>
</view>
</l-list> -->
<l-list tag-position="right" is-link="{{false}}" title="认证状态">
<view slot="right-section" class="value">
<l-tag l-class="{{idStateColor[idState]}}" size="mini" shape="circle">
{{t('user:v.idState.' + idState) || '未设置'}}
</l-tag>
</view>
</l-list>
</view>
<view style="flex:1" />
<l-popup show="{{visible}}" content-align="bottom" locked="{{false}}">
<view class='pupop-content'>
<view class="pupop-header">
<view class="close" bind:tap="onPupopCloseMp">
<l-icon name="close" size="24"></l-icon>
</view>
</view>
<view class='pupop-form'>
<block wx:if="{{ attr === 'nickname' }}">
<l-input label="昵称" value="{{nickname}}" placeholder="请输入昵称" bind:lininput="setCustomDataMp" />
</block>
<block wx:elif="{{ attr === 'name' }}">
<l-input label="姓名" value="{{name}}" placeholder="请输入姓名" bind:lininput="setCustomDataMp" />
</block>
<block wx:elif="{{ attr === 'gender' }}">
<l-form-item label="性别" label-width="100rpx">
<l-radio-group current="{{gender}}" placement="row" length="2" bind:linchange="genderChangeMp">
<l-radio l-class="l-radio" wx:for="{{genderOptions}}" wx:key="id" key="{{item.value}}" placement="left">
{{item.label}}
</l-radio>
</l-radio-group>
</l-form-item>
</block>
</block>
<block wx:elif="{{ attr === 'birth' }}">
<picker mode="date" end="{{birthEnd}}" value="{{birthText}}" bind:change="birthChangeMp">
<l-input label="出生日期" value="{{birthText2 || birthText || '选择日期'}}" disabled="{{true}}" l-label-class="label">
<l-icon slot="right" name="right" size="20" />
</l-input>
</picker>
</block>
</view>
<l-button size="long" bind:lintap="onConfirmMp">
提交
</l-button>
</view>
</l-popup>
</view>

View File

@ -0,0 +1,16 @@
{
"avatar": "头像",
"mobile": "手机号",
"password": "密码",
"manage": "管理",
"bind": "绑定",
"syncWeChat": "同步微信信息",
"send": "发送验证码",
"cancel": "取消",
"unbind": "解绑",
"Mobile-Number-Verification": "手机号验证",
"unbindingWechat": "确定解绑微信账号",
"placeholder": {
"Captcha": "输入4位短信验证码"
}
}

View File

@ -0,0 +1,38 @@
.container {
background: var(--oak-bg-color-page);
display: flex;
flex-direction: column;
flex: 1;
height: 100vh;
}
.syncWeChat {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-top: 10px;
}
.avatar_container {
height: 220px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.avatar {
width: 84px;
height: 84px;
border-radius: 42px;
margin-bottom: 10px;
}
}
.list {
flex: 1;
margin-top: 10px;
margin-bottom: 10px;
background: var(--oak-bg-color-container);
}

32
es/components/user/info/web.d.ts vendored Normal file
View File

@ -0,0 +1,32 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
type DataProps = {
visible: boolean;
nickname: string;
name: string;
birth: string;
gender: string;
mobile: string;
avatarUrl: string;
attr: string;
genderOptions: Array<{
label: string;
value: string;
}>;
attrs: Record<string, string>;
id: string;
refreshing: boolean;
isSupportSyncWeChat: boolean;
appId: string;
};
type MethodsProps = {
goAddMobile: () => void;
goChangePassword: () => void;
setAvatar: () => void;
setVisible: (visible: boolean, attr: string) => void;
setCustomData: (attr: string, value: string | number) => void;
onConfirm: (attr: string) => Promise<void>;
refreshWechatPublicUserInfo: () => void;
};
export default function render(props: WebComponentProps<EntityDict, 'user', false, DataProps, MethodsProps>): import("react/jsx-runtime").JSX.Element;
export {};

View File

@ -0,0 +1,49 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { List, DatePicker, Popup, Form, Button, Input, Radio, Space, } from 'antd-mobile';
import dayjs from 'dayjs';
import OakAvatar from '../../../components/extraFile/avatar';
import Style from './mobile.module.less';
export default function render(props) {
const { data, methods } = props;
const { t, clean, setAvatar, setVisible, goAddMobile, refreshWechatPublicUserInfo, goChangePassword, } = methods;
const { oakFullpath, visible, nickname, name, birth, gender, mobile, avatarUrl, attr, id, isSupportSyncWeChat, refreshing, } = data;
return (_jsxs("div", { className: Style.container, children: [_jsxs(List, { className: Style.list, children: [_jsx(List.Item, { extra: _jsx("div", { style: { marginTop: 5, marginBottom: 5 }, children: _jsx(OakAvatar, { oakAutoUnmount: true, oakPath: oakFullpath
? oakFullpath + '.extraFile$entity'
: undefined, entity: "user", entityId: id, autoUpload: true }) }), children: "\u5934\u50CF" }), _jsx(List.Item, { extra: nickname ? nickname : '未设置', onClick: () => {
setVisible(true, 'nickname');
}, children: t('user:attr.nickname') }), _jsx(List.Item, { extra: gender ? t(`user:v.gender.${gender}`) : '未设置', onClick: () => {
setVisible(true, 'gender');
}, children: t('user:attr.gender') }), _jsx(List.Item, { extra: birth ? dayjs(birth).format('YYYY-MM-DD') : '未设置', onClick: () => {
setVisible(true, 'birth');
}, children: t('user:attr.birth') }), _jsx(List.Item, { extra: mobile ? mobile : '未设置', onClick: () => {
goAddMobile();
}, children: t('mobile') }), _jsx(List.Item, { extra: '********', onClick: () => {
goChangePassword();
}, children: t('password') })] }), _jsx(Popup, { visible: visible, onMaskClick: () => {
clean();
setVisible(false, attr);
}, bodyStyle: {
borderTopLeftRadius: '8px',
borderTopRightRadius: '8px',
minHeight: '20vh',
}, children: _jsx(AttrUpsert, { data: data, methods: methods }) })] }));
}
function AttrUpsert(props) {
const { data, methods } = props;
const { attr, genderOptions, attrs } = data;
const { setCustomData, onConfirm, setVisible } = methods;
const label = attrs[attr];
return (_jsx("div", { children: _jsxs(Form, { footer: _jsx(Button, { block: true, type: "submit", color: "primary", size: "large", onClick: async () => {
await onConfirm(attr);
}, children: "\u63D0\u4EA4" }), children: [_jsx(Form.Header, { children: "\u4FEE\u6539\u4FE1\u606F" }), attr === 'nickname' && (_jsx(Form.Item, { name: attr, label: label, rules: [{ required: true }], children: _jsx(Input, { placeholder: `请输入${label}`, defaultValue: data[attr], onChange: (value) => {
setCustomData(attr, value);
} }) })), attr === 'gender' && (_jsx(Form.Item, { name: attr, label: label, rules: [{ required: true }], children: _jsx(Radio.Group, { defaultValue: data[attr], onChange: (value) => {
setCustomData(attr, value);
}, children: _jsx(Space, { direction: "vertical", children: genderOptions.map((ele) => (_jsx(Radio, { value: ele.value, children: ele.label }))) }) }) })), attr === 'birth' && (_jsx(Form.Item, { name: attr, label: label, onClick: (e, datePickerRef) => {
datePickerRef.current?.open();
}, children: _jsx(DatePicker, { defaultValue: data[attr] ? dayjs(data[attr]).toDate() : null, onConfirm: (value) => {
setCustomData(attr, dayjs(value).startOf('day').valueOf());
}, max: dayjs().toDate(), children: (value) => value
? dayjs(value).format('YYYY-MM-DD')
: '请选择日期' }) }))] }) }));
}

View File

@ -0,0 +1,7 @@
.container {
background: var(--oak-bg-color-container);
box-shadow: 0 2px 3px #0000001a;
border-radius: 3px;
padding: 30px 32px;
}

27
es/components/user/info/web.pc.d.ts vendored Normal file
View File

@ -0,0 +1,27 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'user', false, {
visible: boolean;
nickname: string;
name: string;
birth: string;
gender: string;
mobile: string;
avatarUrl: string;
attr: string;
showBack: boolean;
genderOptions: Array<{
label: string;
value: string;
}>;
wechatUser: EntityDict['wechatUser']['Schema'];
counter: number;
isRoot: boolean;
}, {
goUserManage: () => void;
goAddMobile: () => void;
sendCaptcha: () => void;
goChangePassword: () => void;
updateMyInfo: () => void;
unbindingWechat: (captcha?: string) => void;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,89 @@
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useState } from 'react';
import { Space, Button, Input, Radio, DatePicker, Form, Typography, Modal, Descriptions } from 'antd';
import dayjs from 'dayjs';
import PageHeader from '../../../components/common/pageHeader';
import OakAvatar from '../../../components/extraFile/avatar';
import ExtraFileCommit from '../../../components/extraFile/commit';
import MobileLogin from '../../../components/mobile/login';
import WechatLoginQrCode from '../../../components/wechatLogin/qrCode';
import WechatUserList from '../../../components/wechatUser/bindingList';
import { isCaptcha, } from 'oak-domain/lib/utils/validator';
import Style from './web.module.less';
export default function Render(props) {
const { data, methods } = props;
const { t, updateMyInfo, goAddMobile, sendCaptcha, unbindingWechat, goChangePassword, goUserManage, } = methods;
const { nickname, name, birth, gender, mobile, avatarUrl, showBack, oakExecuting, genderOptions, oakFullpath, oakDirty, wechatUser, counter, isRoot, } = data;
const [open, setOpen] = useState(false);
const [open2, setOpen2] = useState(false);
const [open3, setOpen3] = useState(false);
const [captcha, setCaptcha] = useState('');
return (_jsxs(PageHeader, { title: "\u4E2A\u4EBA\u4FE1\u606F", showBack: showBack, children: [_jsxs("div", { className: Style.container, children: [_jsx(Descriptions, { title: '基本信息' }), _jsxs(Form, { labelCol: { xs: { span: 4 }, md: { span: 6 } }, wrapperCol: { xs: { span: 16 }, md: { span: 12 } }, children: [_jsx(Form.Item, { label: t('avatar'), name: "extraFile$entity", children: _jsx(_Fragment, { children: _jsx(OakAvatar, { oakAutoUnmount: true, oakPath: oakFullpath
? oakFullpath + '.extraFile$entity'
: undefined, entity: "user" }) }) }), _jsx(Form.Item, { label: t('user:attr.name'), rules: [
{
required: true,
},
], children: _jsx(_Fragment, { children: _jsx(Input, { placeholder: "", onChange: (e) => methods.update({
name: e.target.value,
}), value: name }) }) }), _jsx(Form.Item, { label: t('user:attr.nickname'), rules: [
{
required: true,
},
], children: _jsx(_Fragment, { children: _jsx(Input, { placeholder: "", onChange: (e) => methods.update({
nickname: e.target.value,
}), value: nickname }) }) }), _jsx(Form.Item, { label: t('user:attr.gender'), children: _jsx(Space, { direction: "vertical", children: _jsx(Radio.Group, { value: data.gender, options: genderOptions, onChange: ({ target: { value } }) => {
methods.update({ gender: value });
} }) }) }), _jsx(Form.Item, { label: t('user:attr.birth'), children: _jsx(_Fragment, { children: _jsx(DatePicker, { placeholder: "\u8BF7\u9009\u62E9", format: "YYYY-MM-DD", inputReadOnly: true, allowClear: false, value: birth ? dayjs(birth) : undefined, disabledDate: (current) => {
if (dayjs(current).valueOf() >
dayjs().valueOf()) {
return true;
}
return false;
}, onChange: (value) => {
if (value) {
methods.update({
birth: dayjs(value).valueOf(),
});
}
} }) }) }), _jsx(Form.Item, { wrapperCol: {
xs: { offset: 4 },
md: { offset: 6 },
}, children: _jsx(Space, { children: _jsx(ExtraFileCommit, { oakPath: oakFullpath }) }) })] })] }), _jsx("div", { style: { marginTop: '10px' } }), _jsxs("div", { className: Style.container, children: [_jsx(Descriptions, { title: '安全信息' }), _jsxs(Form, { labelCol: { xs: { span: 4 }, md: { span: 6 } }, wrapperCol: { xs: { span: 16 }, md: { span: 12 } }, children: [_jsx(Form.Item, { label: t('mobile'), children: _jsxs(Space, { children: [_jsx(Typography, { children: mobile || '未设置' }), _jsx(Button, { size: "small", onClick: () => {
if (mobile) {
goAddMobile();
return;
}
setOpen(true);
}, children: mobile ? t('manage') : t('bind') })] }) }), _jsx(Form.Item, { label: t('user:attr.password'), children: _jsxs(Space, { children: [_jsx(Typography, { children: '********' }), _jsx(Button, { size: "small", onClick: () => {
goChangePassword();
return;
}, children: t('manage') })] }) }), process.env.NODE_ENV === 'development' && (_jsx(Form.Item, { label: "\u5FAE\u4FE1\u5E10\u53F7", children: _jsx(_Fragment, { children: wechatUser ? (_jsxs(Space, { children: [_jsx(Typography, { children: wechatUser.nickname }), _jsx(WechatUserList, { oakPath: oakFullpath
? `${oakFullpath}.wechatUser$user`
: undefined })] })) : (_jsx(Button, { size: "small", onClick: () => {
setOpen2(true);
}, children: "\u7ED1\u5B9A" })) }) })), isRoot && (_jsx(Form.Item, { label: '系统用户', tooltip: "\u8D85\u7EA7\u7BA1\u7406\u5458\u53EF\u5BF9\u7CFB\u7EDF\u7528\u6237\u8FDB\u884C\u7BA1\u7406", children: _jsx(Button, { size: "small", onClick: () => {
goUserManage();
}, children: t('manage') }) }))] })] }), _jsx(Modal, { title: "\u7ED1\u5B9A\u624B\u673A\u53F7", open: open, destroyOnClose: true, footer: null, onCancel: () => {
setOpen(false);
}, children: _jsx(MobileLogin, { callback: () => {
setOpen(false);
}, oakPath: "$user/info-mobile/login", oakAutoUnmount: true }) }), _jsx(Modal, { title: "\u7ED1\u5B9A\u5FAE\u4FE1", open: open2, destroyOnClose: true, footer: null, maskClosable: false, onCancel: () => {
setOpen2(false);
}, children: _jsx(WechatLoginQrCode, { oakPath: "$user/info-wechatLogin/qrCode", oakAutoUnmount: true }) }), _jsx(Modal, { title: t('Mobile-Number-Verification'), open: open3, destroyOnClose: true, footer: [
_jsx(Button, { onClick: () => setOpen3(false), children: t('cancel') }, "cancel"),
_jsx(Button, { type: "primary", disabled: !isCaptcha(captcha), onClick: () => {
unbindingWechat(captcha);
setOpen3(false);
}, children: t('unbind') }, "send"),
], maskClosable: false, onCancel: () => {
setOpen3(false);
}, children: _jsxs(Space, { direction: "vertical", style: { width: '100%' }, children: [_jsxs(Typography, { children: ["\u8BF7\u8F93\u5165", mobile &&
mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'), "\u6536\u5230\u7684\u9A8C\u8BC1\u7801"] }), _jsx(Form.Item, { name: "captcha", children: _jsx(Input, { allowClear: true, value: captcha, "data-attr": "captcha",
// type="number"
maxLength: 4, placeholder: t('placeholder.Captcha'), size: "large", onChange: (e) => {
setCaptcha(e.target.value);
}, className: Style['loginbox-input'], suffix: _jsx(Button, { type: "link", disabled: counter > 0, onClick: () => sendCaptcha(), children: counter > 0
? `${counter}秒后可重发`
: t('send') }) }) })] }) })] }));
}

View File

@ -0,0 +1,4 @@
/// <reference types="wechat-miniprogram" />
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../../oak-app-domain").EntityDict, "user", false, WechatMiniprogram.Component.DataOption>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,188 @@
// index.ts
export default OakComponent({
entity: 'user',
projection: {
id: 1,
nickname: 1,
name: 1,
userState: 1,
birth: 1,
idState: 1,
gender: 1,
isRoot: 1,
extraFile$entity: {
$entity: 'extraFile',
data: {
id: 1,
tag1: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
type: 1,
entity: 1,
extension: 1,
},
filter: {
tag1: 'avatar',
},
indexFrom: 0,
count: 1,
},
mobile$user: {
$entity: 'mobile',
data: {
id: 1,
mobile: 1,
},
},
ref: {
id: 1,
userState: 1,
},
refId: 1,
},
isList: false,
formData: ({ data: user, features }) => {
const { id, nickname, idState, userState, name, gender, mobile$user, birth, extraFile$entity, } = user || {};
const legalActions = user?.['#oakLegalActions'] || [];
const mobile = mobile$user && mobile$user[0]?.mobile;
const mobileCount = mobile$user ? mobile$user.length : 0;
const mobileText = mobileCount && mobileCount > 1
? `${mobileCount}条手机号`
: mobile || '未设置';
const avatar = features.extraFile.getUrl(extraFile$entity && extraFile$entity[0]);
const reallyRoot = features.token.isReallyRoot();
const currentUserId = features.token.getUserId();
const executableActions = reallyRoot && currentUserId !== id
? legalActions.concat('play')
: legalActions;
return {
id,
nickname,
name,
mobile,
gender,
avatar,
birth: birth ? new Date(birth).toLocaleDateString() : '',
userState,
idState,
mobileCount,
mobileText,
executableActions,
};
},
actions: [
'accept',
'activate',
'disable',
'enable',
'remove',
'update',
'verify',
],
data: {
stateColor: {
shadow: 'primary',
normal: 'success',
disabled: 'danger',
},
idStateColor: {
verifying: 'primary',
verified: 'success',
unverified: 'warning',
},
show: false,
actionDescriptions: {
accept: {
icon: {
name: 'success',
},
label: '通过',
},
activate: {
icon: {
name: 'playon',
},
label: '激活',
},
disable: {
icon: {
name: 'shielding',
},
label: '禁用',
},
enable: {
icon: {
name: 'barrage',
},
label: '启用',
},
remove: {
icon: {
name: 'trash',
},
label: '删除',
},
update: {
icon: {
name: 'editor',
},
label: '更新',
},
verify: {
icon: {
name: 'businesscard',
},
label: '验证',
},
play: {
icon: {
name: 'refresh',
},
label: '切换',
},
},
genderOptions: {
male: '男',
female: '女',
},
},
methods: {
async onActionClick(action) {
switch (action) {
case 'update': {
this.navigateTo({
url: '/user/manage/upsert',
oakId: this.props.oakId,
});
return;
}
case 'enable':
case 'disable':
case 'accept':
case 'verify':
case 'activate': {
await this.execute(action);
break;
}
case 'play': {
const { id } = this.state;
await this.features.token.switchTo(id);
break;
}
default: {
console.error(`尚未实现的action: ${action}`);
}
}
if (action === 'play') {
this.navigateBack(2);
}
},
onActionClickMp(e) {
const { action } = e.detail;
return this.onActionClick(action);
},
},
});

View File

@ -0,0 +1,12 @@
{
"navigationBarTitleText": "用户详情",
"usingComponents": {
"actionPanel": "../../../../components/func/actionPanel/index",
"oak-icon": "../../../../components/icon/index",
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-avatar": "@oak-frontend-base/miniprogram_npm/lin-ui/avatar/index",
"l-list": "@oak-frontend-base/miniprogram_npm/lin-ui/list/index",
"l-icon": "@oak-frontend-base/miniprogram_npm/lin-ui/icon/index",
"l-tag": "@oak-frontend-base/miniprogram_npm/lin-ui/tag/index"
}
}

View File

@ -0,0 +1,84 @@
/** index.wxss **/
@import "../../../../config/styles/mp/index.less";
@import "../../../../config/styles/mp/mixins.less";
.page-body {
height: 100vh;
display: flex;
flex: 1;
flex-direction: column;
background-color: @oak-bg-color-page;
box-sizing: border-box;
.safe-area-inset-bottom();
}
.avatar {
font-size: 128rpx;
color: @oak-text-color-secondary;
height: 140rpx;
width: 140rpx;
border-radius: 14rpx;
}
.nickname {
font-size: 44rpx;
font-weight: bold;
}
.col {
display: flex;
flex-direction: column;
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;
}
}
}
.userInfo {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300rpx;
}
.btn-view {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -0,0 +1,44 @@
<!-- index.wxml -->
<view class="page-body">
<view class="userInfo">
<block wx:if="{{avatar}}">
<l-avatar src="{{avatar}}" />
</block>
<block wx:else>
<l-avatar icon="user" size="140" icon-size="86" />
</block>
</view>
<view class="col">
<l-list is-link="{{false}}" title="昵称">
<view slot="right-section" class="value">{{nickname || '未设置'}}</view>
</l-list>
<l-list is-link="{{false}}" title="姓名">
<view slot="right-section" class="value">{{name || '未设置'}}</view>
</l-list>
<l-list is-link="{{false}}" title="性别">
<view slot="right-section" class="value">{{genderOptions[gender] || '未设置'}}</view>
</l-list>
<l-list is-link="{{false}}" title="生日">
<view slot="right-section" class="value">{{birth || '未设置'}}</view>
</l-list>
<l-list is-link="{{false}}" title="手机号">
<view slot="right-section" class="value">{{mobileText}}</view>
</l-list>
<l-list tag-position="right" is-link="{{false}}" title="用户状态">
<view slot="right-section" class="value">
<l-tag l-class="{{stateColor[userState]}}" size="mini" shape="circle">
{{userState || '未设置'}}
</l-tag>
</view>
</l-list>
<l-list tag-position="right" is-link="{{false}}" title="认证状态">
<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="{{executableActions}}" actionDescriptions="{{actionDescriptions}}" bind:action="onActionClickMp" />
</view>

View File

@ -0,0 +1,5 @@
{
"mobile": "手机号",
"unset": "未设置",
"avatar": "头像"
}

View File

@ -0,0 +1,13 @@
.container {
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--oak-bg-color-page);
}
.list {
margin-bottom: 10px;
}

View File

@ -0,0 +1,25 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../../oak-app-domain';
export default function render(props: WebComponentProps<EntityDict, 'user', false, {
nickname?: string;
avatar?: string;
name?: string;
mobile?: string;
userState?: string;
birth?: string;
idState?: string;
gender?: string;
stateColor: Record<string, string>;
idStateColor: Record<string, string>;
mobileCount: number;
mobileText: string;
actionDescriptions: Record<string, {
icon: {
name: string;
};
label: string;
}>;
executableActions: string[];
}, {
onActionClick: (action: string) => Promise<void>;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,9 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { List, Tag, Avatar } from 'antd-mobile';
import Style from './mobile.module.less';
import ActionPanel from '../../../../components/func/actionPanel';
export default function render(props) {
const { nickname, avatar, name, userState, idState, gender, stateColor, idStateColor, mobileText, executableActions, actionDescriptions, birth, } = props.data;
const { t, onActionClick } = props.methods;
return (_jsxs("div", { className: Style.container, children: [_jsxs(List, { className: Style.list, children: [_jsx(List.Item, { extra: avatar ? _jsx(Avatar, { src: avatar }) : t('unset'), children: t('avatar') }), _jsx(List.Item, { extra: nickname || t('unset'), children: t('user:attr.nickname') }), _jsx(List.Item, { extra: name || t('unset'), children: t('user:attr.name') }), _jsx(List.Item, { extra: gender ? t(`user:v.gender.${gender}`) : t('unset'), children: t('user:attr.gender') }), _jsx(List.Item, { extra: birth || t('unset'), children: t('user:attr.birth') }), _jsx(List.Item, { extra: mobileText, children: t('mobile') }), _jsx(List.Item, { extra: _jsx(Tag, { color: stateColor[userState], children: t(`user:v.userState.${userState}`) }), children: t('user:attr.userState') }), _jsx(List.Item, { extra: _jsx(Tag, { color: idStateColor[idState], children: t(`user:v.idState.${idState}`) }), children: t('user:attr.idState') })] }), _jsx(ActionPanel, { actions: executableActions, actionDescriptions: actionDescriptions, onActionClick: (action) => onActionClick(action) })] }));
}

View File

@ -0,0 +1,25 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../../oak-app-domain';
export default function render(props: WebComponentProps<EntityDict, 'user', false, {
nickname?: string;
avatar?: string;
name?: string;
mobile?: string;
userState?: string;
birth?: string;
idState?: string;
gender?: string;
stateColor: Record<string, string>;
idStateColor: Record<string, string>;
mobileCount: number;
mobileText: string;
actionDescriptions: Record<string, {
icon: {
name: string;
};
label: string;
}>;
executableActions: string[];
}, {
onActionClick: (action: string) => Promise<void>;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,10 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Tag, Avatar, Descriptions } from 'antd';
import Style from './web.module.less';
import ActionPanel from '../../../../components/func/actionPanel';
import PageHeader from '../../../../components/common/pageHeader';
export default function render(props) {
const { nickname, avatar, name, userState, idState, gender, stateColor, idStateColor, mobileText, executableActions, actionDescriptions, birth, } = props.data;
const { t, onActionClick } = props.methods;
return (_jsx(PageHeader, { children: _jsx("div", { className: Style.container, children: _jsxs(Descriptions, { extra: _jsx(ActionPanel, { actions: executableActions, actionDescriptions: actionDescriptions, onActionClick: (action) => onActionClick(action) }), children: [_jsx(Descriptions.Item, { label: t('avatar'), children: avatar ? _jsx(Avatar, { src: avatar }) : t('unset') }), _jsx(Descriptions.Item, { label: t('user:attr.nickname'), children: nickname || t('unset') }), _jsx(Descriptions.Item, { label: t('user:attr.name'), children: name || t('unset') }), _jsx(Descriptions.Item, { label: t('user:attr.gender'), children: gender ? t(`user:v.gender.${gender}`) : t('unset') }), _jsx(Descriptions.Item, { label: t('user:attr.birth'), children: birth || t('unset') }), _jsx(Descriptions.Item, { label: t('mobile'), children: mobileText || t('unset') }), _jsx(Descriptions.Item, { label: t('user:attr.userState'), children: _jsx(Tag, { color: stateColor[userState], children: t(`user:v.userState.${userState}`) }) }), _jsx(Descriptions.Item, { label: t('user:attr.idState'), children: _jsx(Tag, { color: idStateColor[idState], children: t(`user:v.idState.${idState}`) }) })] }) }) }));
}

5
es/components/user/manage/index.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "user", true, {
event: string;
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,110 @@
// index.ts
export default OakComponent({
entity: 'user',
projection: {
id: 1,
nickname: 1,
name: 1,
userState: 1,
idState: 1,
extraFile$entity: {
$entity: 'extraFile',
data: {
id: 1,
tag1: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
type: 1,
entity: 1,
entityId: 1,
extension: 1,
},
filter: {
tag1: 'avatar',
},
indexFrom: 0,
count: 1,
},
mobile$user: {
$entity: 'mobile',
data: {
id: 1,
mobile: 1,
},
},
},
sorters: [
{
sorter: () => ({
$attr: {
$$createAt$$: 1,
},
$direction: 'desc',
}),
},
],
isList: true,
formData: function ({ data: users, features }) {
const userArr = users.map((user) => {
const { id, nickname, userState, name, mobile$user, extraFile$entity, } = user || {};
const mobile = mobile$user && mobile$user[0]?.mobile;
const avatar = features.extraFile.getUrl(extraFile$entity && extraFile$entity[0]);
return {
id,
nickname,
name,
mobile,
avatar,
userState,
};
});
const isRoot = features.token.isReallyRoot();
return {
userArr,
isRoot,
};
},
properties: {
event: '',
},
data: {
stateColor: {
shadow: 'primary',
normal: 'success',
disabled: 'danger',
},
},
methods: {
async bindClicked(input) {
// resolveInput拿的是target原来代码拿的是currentTarget
const { dataset } = this.resolveInput(input);
const { id } = dataset;
this.onCellClicked(id);
},
async onCellClicked(id) {
const { event } = this.props;
if (event) {
this.pubEvent(event, this.state.userArr.find((ele) => ele.id === id));
}
else {
this.navigateTo({
url: '/user/manage/detail',
oakId: id,
});
}
},
goNewUser() {
this.navigateTo({
url: '/user/manage/upsert',
});
},
},
lifetimes: {
detached() {
this.unsubAllEvents(this.props.event);
},
},
});

View File

@ -0,0 +1,9 @@
{
"navigationBarTitleText": "用户管理",
"usingComponents": {
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-icon": "@oak-frontend-base/miniprogram_npm/lin-ui/icon/index",
"l-tag": "@oak-frontend-base/miniprogram_npm/lin-ui/tag/index",
"l-avatar": "@oak-frontend-base/miniprogram_npm/lin-ui/avatar/index"
}
}

View File

@ -0,0 +1,77 @@
/** index.wxss **/
@import "../../../config/styles/mp/index.less";
@import "../../../config/styles/mp/mixins.less";
.page-body {
height: 100vh;
display: flex;
flex: 1;
flex-direction: column;
background-color: @oak-bg-color-page;
box-sizing: border-box;
.safe-area-inset-bottom();
}
.user-list {
flex: 1;
display: flex;
flex-direction: column;
}
.user-info {
display: flex;
flex-direction: column;
flex: 1;
margin-left: 36rpx;
}
.cell {
display: flex;
align-items: center;
padding: 26rpx;
border-bottom: 1px solid #e0e0e0;
background-color: #fff;
}
.row {
display: flex;
align-items: center;
min-width: 300rpx;
justify-content: space-between;
margin-bottom: 20rpx;
.primary {
background-color: @oak-color-primary;
}
.success {
background-color: @oak-color-success;
}
.danger {
background-color: @oak-color-error;
}
}
.row2 {
display: flex;
align-items: center;
font-size: small;
.label {
min-width: 100rpx;
color: @oak-text-color-primary;
}
.value {
color: @oak-text-color-secondary;
}
}
.add {
position: fixed;
right: 20rpx;
z-index: 10;
bottom: constant(safe-area-inset-bottom) !important;
/* 兼容 iOS < 11.2 */
bottom: env(safe-area-inset-bottom) !important;
/* 兼容 iOS >= 11.2 */
}

View File

@ -0,0 +1,35 @@
<!-- index.wxml -->
<view class="page-body">
<view class="user-list">
<view wx:for="{{userArr}}" wx:key="index">
<view class="cell" bind:tap="bindClicked" data-id="{{item.id}}">
<block wx:if="{{item.avatar}}">
<l-avatar src="{{item.avatar}}" />
</block>
<block wx:else>
<l-avatar size="120" icon-size="76" icon="user" />
</block>
<view class="user-info">
<view class="row">
<view class="nickname">{{item.nickname || '未设置'}}</view>
<l-tag l-class="{{stateColor[item.userState]}}" size="mini" shape="circle">
{{item.userState || '未设置'}}
</l-tag>
</view>
<view class="row2">
<view class="label">姓名:</view>
<view class="value">{{item.name || '未设置'}}</view>
</view>
<view class="row2">
<view class="label">手机号:</view>
<view class="value">{{item.mobile || '未绑定'}}</view>
</view>
</view>
<l-icon name="right" size="18" color="#888" />
</view>
</view>
</view>
<l-button l-class="add" special="{{true}}" bind:lintap="goNewUser">
<l-icon name="add" size="80" />
</l-button>
</view>

View File

@ -0,0 +1,41 @@
.container {
height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--oak-bg-color-page);
}
.avatar {
height: 56px;
width: 56px;
background-color: var(--oak-color-primary);
.text {
color: #fff;
font-size: 16px;
}
}
.description {
.row {
.label {
color: var(--oak-text-color-secondary);
font-size: 12px;
}
.value {
color: var(--oak-text-color-primary);
font-size: 12px;
}
}
}
.fab {
position: fixed;
bottom: 50px;
right: 10px;
z-index: 1;
}

View File

@ -0,0 +1,5 @@
/// <reference types="wechat-miniprogram" />
/// <reference types="react" />
import { EntityDict } from "../../../../oak-app-domain";
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "user", false, WechatMiniprogram.Component.DataOption>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,138 @@
const GenderOptions = [
{
value: 'male', label: '男',
},
{
value: 'female', label: '女',
}
];
const IDCardTypeOptions = [
{
value: 'ID-Card', label: '身份证',
},
{
value: 'passport', label: '护照',
},
{
value: 'Mainland-passport', label: '港澳通行证',
}
];
export default OakComponent({
entity: 'user',
projection: {
id: 1,
name: 1,
nickname: 1,
birth: 1,
gender: 1,
idCardType: 1,
idNumber: 1,
extraFile$entity: {
$entity: 'extraFile',
data: {
id: 1,
tag1: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
type: 1,
entity: 1,
extension: 1,
},
filter: {
tag1: 'avatar',
},
indexFrom: 0,
count: 1,
},
},
isList: false,
formData({ data: user }) {
const isRoot = this.features.token.isRoot();
const { birth, gender, idCardType } = user || {};
const birthDate = birth && new Date(birth);
const birthText = birthDate && birthDate.toLocaleDateString();
const birthDayValue = birthDate &&
`${birthDate.getFullYear()}-${birthDate.getMonth() + 1}-${birthDate.getDate()}`;
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, {
isRoot,
birthText,
birthDayValue,
genderText,
idCardTypeText,
idCardTypeOptionIndex,
oldestBirthday: `${now.getFullYear() - 120}-01-01`,
today: `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`,
genderOptionIndex,
idCardTypeIndex,
});
},
data: {
birthEnd: '',
GenderOptions,
IDCardTypeOptions,
birthVisible: false,
},
lifetimes: {
ready() {
const today = new Date();
const birthEnd = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`;
this.setState({ birthEnd });
},
},
methods: {
setValueMp(input) {
const { detail, target: { dataset }, } = input;
const { attr } = dataset;
const { value } = detail;
this.update({ [attr]: value });
},
async confirm() {
const { nickname } = this.state;
if (!nickname) {
this.setMessage({
type: 'warning',
content: '请输入昵称'
});
return;
}
await this.execute();
this.navigateBack();
},
onBirthChange(e) {
const { detail: { value }, } = e;
const birth = new Date(value);
this.update({ birth });
},
onIdCardTypeChange(e) {
const { detail: { value: index }, } = e;
const { value } = IDCardTypeOptions[index];
this.update({
idCardType: value,
});
},
onGenderChange(e) {
const { detail: { value: index }, } = e;
const { value } = GenderOptions[index];
this.update({
gender: value,
});
},
setMobile() {
this.navigateTo({
url: '/mobile/me',
});
},
},
});

View File

@ -0,0 +1,11 @@
{
"navigationBarTitleText": "编辑用户信息",
"usingComponents": {
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-list": "@oak-frontend-base/miniprogram_npm/lin-ui/list/index",
"l-input": "@oak-frontend-base/miniprogram_npm/lin-ui/input/index",
"l-icon": "@oak-frontend-base/miniprogram_npm/lin-ui/icon/index",
"mobile-manage-list": "../../../../components/mobile/manageList/index",
"oak-extraFile-avatar": "../../../../components/extraFile/avatar/index"
}
}

View File

@ -0,0 +1,59 @@
/** index.wxss **/
@import "../../../../config/styles/mp/index.less";
@import "../../../../config/styles/mp/mixins.less";
.page-body {
height: 100vh;
display: flex;
flex: 1;
flex-direction: column;
background-color: @oak-bg-color-page;
box-sizing: border-box;
.safe-area-inset-bottom();
}
.list-box {
padding-left: 25rpx;
padding-right: 25rpx;
}
.avatar {
margin: 10rpx 0;
}
.col {
display: flex;
flex-direction: column;
background-color: #fff;
.label {
color: @oak-text-color-primary;
}
}
.list-item {
position: relative;
font-size: 28rpx;
color: #333;
min-height: 88rpx;
display: flex;
flex-direction: row;
align-items: center;
padding-right: 25rpx;
box-sizing: border-box;
.label {
width: 100px;
display: flex;
flex-direction: row;
align-items: center;
height: 88rpx;
padding-left: 25rpx;
padding-right: 15rpx;
box-sizing: border-box;
}
.mobile-box {
flex: 1;
}
}

View File

@ -0,0 +1,51 @@
<!-- index.wxml -->
<view class="page-body">
<view class="col">
<view class="list-box">
<l-list title="头像">
<view slot="right-section" class="avatar">
<oak-extraFile-avatar oakAutoUnmount="{{true}}" oakPath="{{ oakFullpath ? oakFullpath + '.extraFile$entity' : undefined }}" entity="user"/>
</view>
</l-list>
</view>
<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>
<block wx:if="{{isRoot}}">
<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 class="list-item">
<view class="label">手机号</view>
<view class="mobile-box">
<mobile-manage-list oakPath="{{oakFullpath ? oakFullpath + '.mobile$user' : undefined }}"/>
</view>
</view>
</block>
<block wx:else>
<view class="list-box">
<l-list title="手机号" bind:lintap="setMobile">
<view slot="right-section" class="value">{{mobile || '未绑定'}}</view>
</l-list>
</view>
</block>
</view>
<view style="flex: 1" />
<l-button type="default" disabled="{{oakExecuting || !oakDirty}}" loading="{{oakExecuting}}" size="long" bind:lintap="confirm">
{{t('common::action.confirm')}}
</l-button>
</view>

View File

@ -0,0 +1,12 @@
.container {
height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--oak-bg-color-container);
}
.radio {
margin-right: 10px;
}

View File

@ -0,0 +1,20 @@
import { EntityDict } from '../../../../oak-app-domain';
import { WebComponentProps } from 'oak-frontend-base';
export default function Render(props: WebComponentProps<EntityDict, 'user', false, {
nickname?: string;
name?: string;
gender?: string;
birth?: string;
idCardType?: string;
idNumber?: string;
GenderOptions: Array<{
value: 'male' | 'female';
label: string;
}>;
IDCardTypeOptions: Array<{
value: string;
label: string;
}>;
}, {
confirm: () => void;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,31 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState } from 'react';
import { Button, Input, Form, Radio, DatePicker, Space } from 'antd-mobile';
import dayjs from 'dayjs';
import Style from './mobile.module.less';
export default function Render(props) {
const { data, methods } = props;
const { GenderOptions, IDCardTypeOptions } = data;
const { t, update, setDisablePulldownRefresh, confirm } = methods;
const [birthPickerVisible, setBirthPickerVisible] = useState(false);
return (_jsxs("div", { className: Style.container, children: [_jsxs(Form, { layout: "horizontal", children: [_jsx(Form.Item, { label: t('user:attr.nickname'), rules: [{ required: true }], children: _jsx(Input, { onChange: (val) => update({ nickname: val }), value: data.nickname || '' }) }), _jsx(Form.Item, { label: t('user:attr.name'), children: _jsx(Input, { onChange: (val) => update({ name: val }), value: data.name || '' }) }), _jsx(Form.Item, { label: t('user:attr.birth'), onClick: () => {
setBirthPickerVisible(true);
setDisablePulldownRefresh(true);
}, children: _jsx(Input, { value: data.birth
? dayjs(data.birth).format('YYYY-MM-DD')
: '', readOnly: true }) }), _jsx(Form.Item, { label: t('user:attr.gender'), children: _jsx(Radio.Group, { onChange: (e) => {
update({
gender: e,
});
}, value: data.gender, children: _jsx(Space, { direction: "horizontal", children: GenderOptions.map((ele, idx) => (_jsx(Radio, { value: ele.value, className: Style.radio, children: ele.label }, idx))) }) }) }), _jsx(Form.Item, { label: t('user:attr.idCardType'), children: _jsx(Radio.Group, { onChange: (e) => {
update({
idCardType: e,
});
}, value: data.idCardType, children: _jsx(Space, { direction: "vertical", children: IDCardTypeOptions.map((ele, idx) => (_jsx(Radio, { value: ele.value, className: Style.radio, children: ele.label }, idx))) }) }) }), _jsx(Form.Item, { label: t('user:attr.idNumber'), children: _jsx(Input, { onChange: (val) => update({ idNumber: val }), value: data.idNumber || '' }) })] }), _jsx(DatePicker, { visible: birthPickerVisible, max: new Date(), min: new Date('1900-01-01'), onConfirm: (value) => {
const val = value.valueOf();
update({ birth: val });
}, onClose: () => {
setBirthPickerVisible(false);
setDisablePulldownRefresh(false);
} }), _jsx("div", { style: { flex: 1 } }), _jsx(Button, { block: true, color: "primary", onClick: () => confirm(), children: t('common::action.confirm') })] }));
}

View File

@ -5,4 +5,4 @@
box-shadow: 0 2px 3px #0000001a;
border-radius: 3px;
padding: 30px 32px;
}
}

View File

@ -0,0 +1,20 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../../oak-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'user', false, {
nickname?: string;
name?: string;
gender?: string;
birth?: string;
idCardType?: string;
idNumber?: string;
GenderOptions: Array<{
value: 'male' | 'female';
label: string;
}>;
IDCardTypeOptions: Array<{
value: string;
label: string;
}>;
}, {
confirm: () => void;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,21 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Button, Input, Form, Radio, DatePicker } from 'antd';
import dayjs from 'dayjs';
import PageHeader from '../../../../components/common/pageHeader';
import Style from './web.module.less';
export default function Render(props) {
const { data, methods } = props;
const { GenderOptions, IDCardTypeOptions } = data;
const { t, update, confirm } = methods;
return (_jsx(PageHeader, { children: _jsx("div", { className: Style.container, children: _jsxs(Form, { layout: "horizontal", labelCol: { span: 8 }, wrapperCol: { span: 16 }, style: { maxWidth: 600 }, children: [_jsx(Form.Item, { label: t('user:attr.nickname'), required: true, children: _jsx(Input, { onChange: (e) => update({ nickname: e.target.value }), value: data.nickname || '' }) }), _jsx(Form.Item, { label: t('user:attr.name'), children: _jsx(Input, { onChange: (e) => update({ name: e.target.value }), value: data.name || '' }) }), _jsx(Form.Item, { label: t('user:attr.birth'), children: _jsx(DatePicker, { value: data.birth ? dayjs(data.birth) : undefined, format: 'YYYY/MM/DD', onChange: (value) => update({ birth: dayjs(value).valueOf() }) }) }), _jsx(Form.Item, { label: t('user:attr.gender'), children: _jsx(Radio.Group, { onChange: (e) => {
update({
gender: e.target
.value,
});
}, value: data.gender, children: GenderOptions.map((ele, idx) => (_jsx(Radio, { value: ele.value, children: ele.label }, idx))) }) }), _jsx(Form.Item, { label: t('user:attr.idCardType'), children: _jsx(Radio.Group, { onChange: (e) => {
update({
idCardType: e.target
.value,
});
}, value: data.idCardType, children: IDCardTypeOptions.map((ele, idx) => (_jsx(Radio, { value: ele.value, className: Style.radio, children: ele.label }, idx))) }) }), _jsx(Form.Item, { label: t('user:attr.idNumber'), children: _jsx(Input, { onChange: (e) => update({ idNumber: e.target.value }), value: data.idNumber || '' }) }), _jsx(Form.Item, { wrapperCol: { offset: 8, span: 16 }, children: _jsx(Button, { type: "primary", color: "primary", onClick: () => confirm(), children: t('common::action.confirm') }) })] }) }) }));
}

13
es/components/user/manage/web.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function render(props: WebComponentProps<EntityDict, 'user', true, {
userArr: Array<EntityDict['user']['OpSchema'] & {
avatar: string;
mobile: string;
}>;
stateColor: Record<string, string>;
isRoot: boolean;
}, {
onCellClicked: (id: string) => Promise<void>;
goNewUser: () => Promise<void>;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,19 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { List, Tag, Avatar, FloatingBubble } from 'antd-mobile';
import { PlusOutlined } from '@ant-design/icons';
import Style from './mobile.module.less';
export default function render(props) {
const { stateColor, userArr, isRoot } = props.data;
const { onCellClicked, t, goNewUser } = props.methods;
return (_jsxs("div", { className: Style.container, children: [_jsx(List, { children: userArr?.map((ele, index) => {
return (_jsx(List.Item, { onClick: () => onCellClicked(ele.id), prefix: _jsx(Avatar, { className: Style.avatar, src: ele.avatar }), title: _jsx("div", { children: ele.name || '--' }), description: _jsxs("div", { className: Style.description, children: [_jsxs("div", { className: Style.row, children: [_jsx("span", { className: Style.label, children: "\u6635\u79F0:\u00A0" }), _jsx("span", { className: Style.value, children: ele.nickname || '--' })] }), _jsxs("div", { className: Style.row, children: [_jsx("span", { className: Style.label, children: "\u624B\u673A\u53F7:\u00A0" }), _jsx("span", { className: Style.value, children: ele.mobile || '--' })] }), _jsx(Tag, { color: stateColor[ele.userState], children: ele.userState
? t(`user:v.userState.${ele.userState}`)
: '未知' })] }) }, index));
}) }), isRoot && (_jsx(FloatingBubble, { axis: "x", magnetic: "x", style: {
'--initial-position-bottom': '24px',
'--initial-position-right': '24px',
'--edge-distance': '24px',
}, onClick: () => {
goNewUser();
}, children: _jsx(PlusOutlined, {}) }))] }));
}

View File

@ -0,0 +1,8 @@
.container {
background: var(--oak-bg-color-container);
box-shadow: 0 2px 3px #0000001a;
border-radius: 3px;
padding: 30px 32px;
}

13
es/components/user/manage/web.pc.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'user', true, {
userArr: Array<EntityDict['user']['OpSchema'] & {
avatar: string;
mobile: string;
}>;
stateColor: Record<string, string>;
isRoot: boolean;
}, {
onCellClicked: (id: string, event?: string) => Promise<void>;
goNewUser: () => Promise<void>;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,87 @@
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { Table, Tag, Button, Space, Avatar } from 'antd';
import { UserOutlined } from '@ant-design/icons';
import FilterPanel from 'oak-frontend-base/es/components/filterPanel';
import PageHeader from '../../../components/common/pageHeader';
import Style from './web.module.less';
export default function Render(props) {
const { methods, data } = props;
const { t, setPageSize, setCurrentPage, goNewUser, onCellClicked } = methods;
const { oakFullpath, oakLoading, oakPagination, userArr = [], stateColor, isRoot, } = data;
const { pageSize, total, currentPage } = oakPagination || {};
return (_jsx(PageHeader, { children: _jsxs("div", { className: Style.container, children: [isRoot && (_jsx(Space, { style: { marginBottom: 16 }, children: _jsx(Button, { type: "primary", onClick: () => {
goNewUser();
}, children: "\u6DFB\u52A0\u7528\u6237" }) })), _jsx(FilterPanel, { entity: "user", oakPath: oakFullpath, columns: [
{
attr: 'nickname',
op: '$includes',
},
{
attr: 'name',
op: '$includes',
},
{
attr: 'userState',
},
] }), _jsx(Table, { loading: oakLoading, dataSource: userArr, rowKey: "id", columns: [
{
dataIndex: 'id',
title: '#',
render: (value, record, index) => {
return index + 1;
},
},
{
width: 100,
dataIndex: 'avatar',
title: '头像',
render: (value, record, index) => {
if (!value) {
return (_jsx(Avatar, { icon: _jsx(UserOutlined, {}) }));
}
return _jsx(Avatar, { src: value, shape: "circle" });
},
},
{
dataIndex: 'nickname',
title: '昵称',
},
{
dataIndex: 'name',
title: '姓名',
},
{
dataIndex: 'mobile',
title: '手机号',
},
{
dataIndex: 'userState',
title: '状态',
render: (value, record, index) => {
return (_jsx(Tag, { color: stateColor[value], children: t(`user:v.userState.${value}`) }));
},
},
{
dataIndex: 'op',
width: 200,
title: '操作',
align: 'center',
render: (value, record, index) => {
return (_jsx(_Fragment, { children: _jsx(Button, { type: "link", onClick: () => {
onCellClicked(record.id);
}, children: "\u8BE6\u60C5" }) }));
},
fixed: 'right',
},
], pagination: {
total: total,
pageSize: pageSize,
current: currentPage,
onShowSizeChange: (pageSize) => {
setPageSize(pageSize);
},
onChange: (page) => {
setCurrentPage(page);
},
} })] }) }));
}

View File

@ -1 +1,8 @@
{}
{
"navigationBarTitleText": "领取权限",
"usingComponents": {
"l-notice-bar": "@oak-frontend-base/miniprogram_npm/lin-ui/notice-bar/index",
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-list": "@oak-frontend-base/miniprogram_npm/lin-ui/list/index"
}
}

View File

@ -0,0 +1,20 @@
/** index.wxss **/
@import '../../../config/styles/mp/index.less';
@import '../../../config/styles/mp/mixins.less';
.view {
height: 100vh;
display: flex;
flex: 1;
flex-direction: column;
background-color: @oak-bg-color-page;
box-sizing: border-box;
.safe-area-inset-bottom();
}
.btn {
position: fixed;
bottom: 0;
width: 100%;
// margin: 0 10px;
}

View File

@ -0,0 +1,32 @@
<!-- index.wxml -->
<view class="view" wx:if="{{userEntityGrant}}">
<block>
<l-notice-bar show="{{!hideTip && !isGranter && !hasClaimed}}">
{{t('tip')}}
</l-notice-bar>
<l-notice-bar show="{{isGranter}}">
{{t('isGranter')}}
</l-notice-bar>
<l-notice-bar show="{{hasClaimed}}">
{{t('hasClaimed')}}
</l-notice-bar>
</block>
<block wx:if="{{!hideInfo}}">
<view class="info">
<l-list title="{{t('granterName')}}" right-desc="{{userEntityGrant.granter.name || userEntityGrant.granter.nickname}}" is-link="{{false}}" />
<l-list title="{{expired ? t('isExpired') : t('counter')}}" right-desc="{{expired ? t('expired') : counterStr}}" is-link="{{false}}" />
</view>
</block>
<view style="flex: 1" />
<view class="btn">
<l-button
type="{{isGranter || hasClaimed ? 'error' : (!expired ? 'default' : 'warning')}}"
disabled="{{oakExecutable !== true || !!expired || isGranter || hasClaimed}}"
size="long" bind:lintap="claim">
{{isGranter ? t('isGranter') : ( hasClaimed ? t('hasClaimed') : (!expired ? t('userEntityGrant:action.claim') : t('expired')))}}
</l-button>
</view>
</view>

View File

@ -17,11 +17,13 @@ export default OakComponent({
name: 1,
nickname: 1,
},
granteeId: 1,
$$createAt$$: 1,
expired: 1,
expiresAt: 1,
},
actions: [
'disable',
],
properties: {
entity: '',
entityId: '',

View File

@ -1,93 +1,105 @@
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
import { Table, Space, Typography } from 'antd';
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
import { useState } from 'react';
import { Table, Space, Typography, Modal } from 'antd';
import dayjs from 'dayjs';
import ActionBtnPanel from 'oak-frontend-base/es/components/actionBtnPanel';
import UserEntityGrantShare from '../share';
export default function render(props) {
const { oakPagination, oakFullpath, list = [], oakLoading, } = props.data;
const { pageSize, total, currentPage } = oakPagination || {};
const { t, setPageSize, setCurrentPage, execute, updateItem } = props.methods;
return (_jsx(Table, { loading: oakLoading, dataSource: list, rowKey: "id", columns: [
{
dataIndex: 'id',
title: '#',
render: (value, record, index) => {
return index + 1;
},
},
{
dataIndex: 'name',
title: '授权人',
render: (value, record, index) => {
return (_jsxs(Space, { direction: "vertical", children: [_jsxs(Typography.Text, { children: ["\u59D3\u540D\uFF1A", record.granter?.name || '-'] }), _jsxs(Typography.Text, { children: ["\u6635\u79F0\uFF1A", record.granter?.nickname] })] }));
},
},
{
dataIndex: 'type',
title: '授权类型',
render: (value, record, index) => {
return t(`userEntityGrant:v.type.${value}`);
},
},
{
dataIndex: 'relation',
title: '权限',
render: (value, record, index) => {
return (value.display ||
t(`${record.relationEntity}:r.${value.name}`));
},
},
{
dataIndex: '$$createAt$$',
title: '创建时间',
render: (value, record, index) => {
return dayjs(value).format('YYYY-MM-DD HH:mm');
},
},
{
dataIndex: 'expired',
title: '状态',
render: (value, record, index) => {
return (_jsxs(Typography.Text, { type: record.expired ? 'danger' : 'success', children: [record.expired ? '失效' : '有效', !record.expired && (_jsxs(Typography.Text, { children: ["\u00A0", dayjs(record.expiresAt).format('YYYY-MM-DD HH:mm')] }))] }));
},
},
{
width: 200,
title: '操作',
key: 'action',
align: 'center',
fixed: 'right',
render: (value, record, rowIndex) => {
return (_jsx(ActionBtnPanel, { mode: "table-cell", entity: "parasite", items: [
{
label: '失效',
action: 'disable',
// alerted: true,
show: record['#oakLegalActions']?.includes('cancel'),
onClick: () => {
updateItem({ expired: true }, record.id, 'disable');
execute();
},
},
{
label: '二维码',
action: 'qrcode',
show: record['#oakLegalActions']?.includes('qrcode'),
// alerted: true,
onClick: async () => {
},
},
] }));
},
},
], pagination: {
total,
pageSize,
current: currentPage,
onShowSizeChange: (pageSize) => {
setPageSize(pageSize);
},
onChange: (current) => {
setCurrentPage(current);
},
} }));
const [qrCodeOpen, setQrCodeOpen] = useState(false);
const [userEntityGrantId, setUserEntityGrantId] = useState('');
return (_jsxs(_Fragment, { children: [_jsx(Table, { loading: oakLoading, dataSource: list, rowKey: "id", columns: [
{
dataIndex: 'id',
title: '#',
render: (value, record, index) => {
return index + 1;
},
},
{
dataIndex: 'name',
title: '授权人',
render: (value, record, index) => {
return (_jsxs(Space, { direction: "vertical", children: [_jsxs(Typography.Text, { children: ["\u59D3\u540D\uFF1A", record.granter?.name || '-'] }), _jsxs(Typography.Text, { children: ["\u6635\u79F0\uFF1A", record.granter?.nickname] })] }));
},
},
{
dataIndex: 'type',
title: '授权类型',
render: (value, record, index) => {
return t(`userEntityGrant:v.type.${value}`);
},
},
// {
// dataIndex: 'relation',
// title: '权限',
// render: (value, record, index) => {
// return (
// // value.display ||
// t(`${record.relationEntity}:r.${value.name}`)
// );
// },
// },
{
dataIndex: '$$createAt$$',
title: '创建时间',
render: (value, record, index) => {
return dayjs(value).format('YYYY-MM-DD HH:mm');
},
},
{
dataIndex: 'expired',
title: '状态',
render: (value, record, index) => {
return (_jsxs(Typography.Text, { type: record.expired ? 'danger' : 'success', children: [record.expired ? '失效' : '有效', !record.expired && (_jsxs(Typography.Text, { children: ["\u00A0", dayjs(record.expiresAt).format('YYYY-MM-DD HH:mm')] }))] }));
},
},
{
width: 200,
title: '操作',
key: 'action',
align: 'center',
fixed: 'right',
render: (value, record, rowIndex) => {
return (_jsx(ActionBtnPanel, { mode: "table-cell", entity: "userEntityGrant", items: [
{
label: '失效',
action: 'disable',
// alerted: true,
show: record['#oakLegalActions']?.includes('disable'),
onClick: () => {
updateItem({ expired: true }, record.id, 'disable');
execute();
},
},
{
label: '二维码',
// action: 'qrcode',
// show: record[
// '#oakLegalActions'
// ]?.includes('qrcode'),
// alerted: true,
onClick: async () => {
setUserEntityGrantId(record.id);
setQrCodeOpen(true);
},
},
] }));
},
},
], pagination: {
total,
pageSize,
current: currentPage,
onShowSizeChange: (pageSize) => {
setPageSize(pageSize);
},
onChange: (current) => {
setCurrentPage(current);
},
} }), _jsx(Modal, { width: 786, open: qrCodeOpen, destroyOnClose: true, onCancel: () => {
setQrCodeOpen(false);
}, footer: null, children: _jsx(UserEntityGrantShare, { oakId: userEntityGrantId, oakAutoUnmount: true, oakPath: "$userEntityGrant/list-userEntityGrant/detail" }) })] }));
}

View File

@ -1,11 +1,5 @@
<!-- index.wxml -->
<view class="qrcode_view">
<!-- <view class="row">
<text class="text">分享权限</text>
<view class="icon-view">
<g-icon name="person_add" size="30" />
</view>
</view> -->
<view class="col">
<block wx:if="{{oakLoading}}">
<l-loading show="{{true}}" type="circle"></l-loading>

View File

@ -6,7 +6,7 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
entityId: string;
relationEntity: string;
relationEntityFilter: any;
relations: import("../../../oak-app-domain/Relation/Schema").OpSchema[];
relationIds: string[];
type: "grant" | "transfer";
redirectToAfterConfirm: import("../../../oak-app-domain/UserEntityGrant/Schema").RedirectToProps | null | undefined;
claimUrl: string;

View File

@ -22,7 +22,7 @@ export default OakComponent({
entityId: '',
relationEntity: '',
relationEntityFilter: {},
relations: [],
relationIds: [],
type: 'grant',
redirectToAfterConfirm: {},
claimUrl: '',
@ -44,13 +44,17 @@ export default OakComponent({
},
setInit() {
const userId = this.features.token.getUserId();
const { entity, entityId, relationEntity, relationEntityFilter, type, redirectToAfterConfirm, qrCodeType, claimUrl, multiple, rule, ruleOnRow, } = this.props;
const { entity, entityId, relationEntity, relationEntityFilter, relationIds, type, redirectToAfterConfirm, qrCodeType, claimUrl, multiple, rule, ruleOnRow, } = this.props;
this.setState({
userEntityGrantId: '',
});
if (this.isCreation()) {
this.update({
entity,
entityId,
relationEntity,
relationEntityFilter,
relationIds,
type: type || 'grant',
multiple,
rule: rule || 'single',
@ -60,9 +64,9 @@ export default OakComponent({
qrCodeType: qrCodeType,
claimUrl,
});
this.setState({
userEntityGrantId: '',
});
// this.setState({
// userEntityGrantId: '',
// });
}
},
async confirm() {
@ -87,5 +91,5 @@ export default OakComponent({
userEntityGrantId: id,
});
},
}
},
});

View File

@ -1,11 +1,11 @@
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { Form, Space, Button, InputNumber, Typography, } from 'antd';
import UserEntityGrantShare from '../share';
import PageHeader from '../../common/pageHeader';
import Style from './web.module.less';
export default function Render(props) {
const { oakId, oakFullpath, entity, entityId, relationEntity, showBack = true, userEntityGrantId, period } = props.data;
const { setPeriod, confirm, setInit } = props.methods;
console.log(userEntityGrantId);
if (!!userEntityGrantId) {
return (_jsxs("div", { className: Style.container, children: [_jsx(UserEntityGrantShare, { oakId: userEntityGrantId, oakAutoUnmount: true, oakPath: "$userEntityGrant/upsert-userEntityGrant/detail" }), _jsx("div", { style: {
width: '100%',
@ -15,9 +15,9 @@ export default function Render(props) {
setInit();
}, children: "\u91CD\u65B0\u751F\u6210" }) })] }));
}
return (_jsx(PageHeader, { title: "\u521B\u5EFA", showBack: showBack, children: _jsx("div", { className: Style.container, children: _jsxs(Form, { labelCol: { span: 4 }, wrapperCol: { span: 8 }, children: [_jsx(Form.Item, { label: "\u6709\u6548\u671F", required: true, children: _jsx(_Fragment, { children: _jsx(InputNumber, { min: 1, max: 30, placeholder: "\u8BF7\u8F93\u5165", onChange: (value) => {
setPeriod(value);
}, value: period, addonAfter: _jsx(Typography, { children: "\u5929" }) }) }) }), _jsx(Form.Item, { wrapperCol: { offset: 4 }, children: _jsx(Space, { children: _jsx(Button, { type: "primary", onClick: () => {
confirm();
}, children: "\u63D0\u4EA4" }) }) })] }) }) }));
return (_jsx("div", { className: Style.container, children: _jsxs(Form, { labelCol: { span: 4 }, wrapperCol: { span: 8 }, children: [_jsx(Form.Item, { label: "\u6709\u6548\u671F", required: true, children: _jsx(_Fragment, { children: _jsx(InputNumber, { min: 1, max: 30, placeholder: "\u8BF7\u8F93\u5165", onChange: (value) => {
setPeriod(value);
}, value: period, addonAfter: _jsx(Typography, { children: "\u5929" }) }) }) }), _jsx(Form.Item, { wrapperCol: { offset: 4 }, children: _jsx(Space, { children: _jsx(Button, { type: "primary", onClick: () => {
confirm();
}, children: "\u63D0\u4EA4" }) }) })] }) }));
}

View File

@ -23,7 +23,7 @@
style="display: flex;flex-direction: column; flex: 1; margin-top: 30rpx"
oakId="{{userId}}"
mobile="{{mobileValue}}"
oakPath="{{oakFullpath ? oakFullpath + '.user' : undefined}}"
oakPath="{{oakFullpath + '.user'}}"
oakAutoUnmount="{{true}}"
entity="{{entity}}"
entityId="{{entityId}}"

View File

@ -22,5 +22,5 @@ export default function Render(props) {
},
], children: _jsx(_Fragment, { children: _jsx(Input, { maxLength: 11, value: mobileValue, onChange: (value) => {
onMobileChange(value);
}, placeholder: t('inputMobile'), type: "tel", clearable: true }) }) }), mobileValueReady && userId && (_jsx(OnUser, { oakAutoUnmount: true, oakPath: oakFullpath ? `${oakFullpath}.user` : undefined, entity: entity, entityId: entityId, relations: relations, oakId: userId }))] }));
}, placeholder: t('inputMobile'), type: "tel", clearable: true }) }) }), mobileValueReady && userId && (_jsx(OnUser, { oakAutoUnmount: true, oakPath: `${oakFullpath}.user`, entity: entity, entityId: entityId, relations: relations, oakId: userId }))] }));
}

View File

@ -24,7 +24,7 @@ export default function Render(props) {
], children: _jsx(Input, { maxLength: 11, value: mobileValue, onChange: (e) => {
const strValue = e.target.value;
onMobileChange(strValue);
}, placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7\u7801", type: "tel" }) }) }), mobileValueReady && userId && (_jsx(OnUser, { oakAutoUnmount: true, oakPath: oakFullpath ? `${oakFullpath}.user` : undefined, entity: entity, entityId: entityId, relations: relations, oakId: userId, setPasswordConfirm: setPasswordConfirm })), _jsx(Form, { colon: true, labelCol: { span: 4 }, wrapperCol: { span: 8 }, children: _jsx(Form.Item, { wrapperCol: { offset: 4 }, children: _jsxs(Space, { children: [_jsx(Button, { style: { flex: 2 }, type: "primary", htmlType: "reset", onClick: async () => {
}, placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7\u7801", type: "tel" }) }) }), mobileValueReady && userId && (_jsx(OnUser, { oakAutoUnmount: true, oakPath: `${oakFullpath}.user`, entity: entity, entityId: entityId, relations: relations, oakId: userId, setPasswordConfirm: setPasswordConfirm })), _jsx(Form, { colon: true, labelCol: { span: 4 }, wrapperCol: { span: 8 }, children: _jsx(Form.Item, { wrapperCol: { offset: 4 }, children: _jsxs(Space, { children: [_jsx(Button, { style: { flex: 2 }, type: "primary", htmlType: "reset", onClick: async () => {
await onConfirm();
setPasswordConfirm(true);
}, disabled: !legal ||

View File

@ -1,5 +1,5 @@
<view class="body">
<relation-on-user style="display: flex;flex-direction: column; flex: 1; margin-top: 30rpx" oakAutoUnmount="{{true}}" oakPath="{{oakFullpath ? oakFullpath + '.user' : undefined}}" entity="{{entity}}" entityId="{{entityId}}" relations="{{relations}}" oakId="{{oakId}}" />
<relation-on-user style="display: flex;flex-direction: column; flex: 1; margin-top: 30rpx" oakAutoUnmount="{{true}}" oakPath="{{oakFullpath + '.user'}}" entity="{{entity}}" entityId="{{entityId}}" relations="{{relations}}" oakId="{{oakId}}" />
<view style="flex: 1" />
<l-button disabled="{{!oakDirty}}" size="long" bind:lintap="onConfirm">

View File

@ -6,5 +6,5 @@ export default function Render(props) {
const { onConfirm, onReset, t } = props.methods;
return (_jsx(Form, { footer: _jsxs(Space, { children: [_jsx(Button, { color: "primary", style: { flex: 2 }, onClick: () => {
onConfirm();
}, disabled: oakExecutable !== true, children: t('common::action.confirm') }), _jsx(Button, { style: { flex: 1 }, onClick: () => onReset(), children: t('common::reset') })] }), children: _jsx(OnUser, { oakAutoUnmount: true, oakPath: oakFullpath && `${oakFullpath}.user`, entity: entity, entityId: entityId, relations: relations, oakId: oakId }) }));
}, disabled: oakExecutable !== true, children: t('common::action.confirm') }), _jsx(Button, { style: { flex: 1 }, onClick: () => onReset(), children: t('common::reset') })] }), children: _jsx(OnUser, { oakAutoUnmount: true, oakPath: `${oakFullpath}.user`, entity: entity, entityId: entityId, relations: relations, oakId: oakId }) }));
}

View File

@ -4,5 +4,5 @@ import OnUser from '../onUser/index';
export default function Render(props) {
const { relations, entity, entityId, oakId, oakDirty, oakFullpath } = props.data;
const { onConfirm, onReset, t } = props.methods;
return (_jsxs(_Fragment, { children: [_jsx(OnUser, { oakAutoUnmount: true, oakPath: oakFullpath && `${oakFullpath}.user`, entity: entity, entityId: entityId, relations: relations, oakId: oakId }), _jsx(Form, { colon: true, labelCol: { span: 4 }, wrapperCol: { span: 8 }, children: _jsx(Form.Item, { wrapperCol: { offset: 4 }, children: _jsxs(Space, { children: [_jsx(Button, { disabled: !oakDirty, type: "primary", onClick: () => onConfirm(), children: t('common::action.confirm') }), _jsx(Button, { htmlType: "reset", onClick: () => onReset(), children: t('common::reset') })] }) }) })] }));
return (_jsxs(_Fragment, { children: [_jsx(OnUser, { oakAutoUnmount: true, oakPath: `${oakFullpath}.user`, entity: entity, entityId: entityId, relations: relations, oakId: oakId }), _jsx(Form, { colon: true, labelCol: { span: 4 }, wrapperCol: { span: 8 }, children: _jsx(Form.Item, { wrapperCol: { offset: 4 }, children: _jsxs(Space, { children: [_jsx(Button, { disabled: !oakDirty, type: "primary", onClick: () => onConfirm(), children: t('common::action.confirm') }), _jsx(Button, { htmlType: "reset", onClick: () => onReset(), children: t('common::reset') })] }) }) })] }));
}

View File

@ -15,9 +15,11 @@ export default OakComponent({
qrCodeType: 1,
},
isList: false,
formData: ({ data: userEntityGrant }) => ({
userEntityGrant,
}),
formData({ data: userEntityGrant, props }) {
return {
userEntityGrant,
};
},
properties: {
entity: '',
entityId: '',
@ -106,12 +108,26 @@ export default OakComponent({
},
setRelation(value) {
this.update({
relationIds: [value],
relationIds: value,
});
},
setRelationMp(e) {
const { currentKey } = e.detail;
this.setRelation(currentKey);
const { key } = e.detail;
const { userEntityGrant } = this.state;
const relationIds = [...(userEntityGrant?.relationIds || [])];
const index = relationIds.findIndex((ele) => ele === key);
if (index > -1) {
relationIds.splice(index, 1);
}
else {
relationIds.push(key);
}
// 小程序 多选处理
const newRelations = this.props.relations?.map((ele) => Object.assign({}, ele, {
checked: relationIds.includes(ele.id),
}));
this.setState({ relations: newRelations });
this.setRelation(relationIds);
},
setNumber(value) {
this.update({
@ -129,6 +145,20 @@ export default OakComponent({
const { count } = e.detail;
this.setPeriod(count);
},
setMultiple(m) {
this.update({ multiple: m });
},
setMultipleMp(e) {
const { value } = e.detail;
this.setMultiple(value);
},
setRule(m) {
this.update({ rule: m });
},
setRuleMp(e) {
const { currentKey } = e.detail;
this.setRule(currentKey);
},
setUnit(u) {
const { defaultPeriods } = this.state;
this.setState({ unit: u });

View File

@ -10,7 +10,10 @@
"l-list": "@oak-frontend-base/miniprogram_npm/lin-ui/list/index",
"l-radio": "@oak-frontend-base/miniprogram_npm/lin-ui/radio/index",
"l-radio-group": "@oak-frontend-base/miniprogram_npm/lin-ui/radio-group/index",
"l-checkbox-group": "@oak-frontend-base/miniprogram_npm/lin-ui/checkbox-group/index",
"l-checkbox": "@oak-frontend-base/miniprogram_npm/lin-ui/checkbox/index",
"l-counter": "@oak-frontend-base/miniprogram_npm/lin-ui/counter/index",
"userEntityGrant-detail": "../../../../pages/userEntityGrant/detail/index"
"l-switch": "@oak-frontend-base/miniprogram_npm/lin-ui/switch/index",
"userEntityGrant-share": "../../../userEntityGrant/share/index"
}
}

View File

@ -1,112 +1,68 @@
<!-- index.wxml -->
<view class="page-body">
<block wx:if="{{!!userEntityGrantId}}">
<l-notice-bar
front-icon-name="notification"
show="{{true}}"
>
<block wx:if="{{!!userEntityGrantId}}">
<l-notice-bar front-icon-name="notification" show="{{true}}">
请通过分享或者截屏二维码方式分享权限
</l-notice-bar>
<view class="ueg-container">
<userEntityGrant-detail
oakId="{{userEntityGrantId}}"
oakAutoUnmount="{{true}}"
oakPath="$userRelation/upsert/byUserEntityGrant-userEntityGrant/detail"
/>
<view
class="share"
>
<l-button
size="medium"
type="success"
open-type="share"
>
<userEntityGrant-share oakId="{{userEntityGrantId}}" oakAutoUnmount="{{true}}" oakPath="$userRelation/upsert/byUserEntityGrant-userEntityGrant/detail" />
<view class="share">
<l-button size="medium" type="success" open-type="share">
分享
</l-button>
</view
</view>
</block>
<block wx:else>
<view class="list-item">
<view class="label">权限:</view>
<view class="value">
<l-radio-group
placement="row"
l-class="radio-container"
bind:linchange="setRelationMp"
current="{{userEntityGrant.relationId}}"
>
<l-radio
wx:for-items="{{relations}}"
wx:key="index"
key="{{item.id}}"
l-class="radio"
size="48rpx"
>
{{item.display || t(entity + ':r.' + item.name)}}
</l-radio>
</l-radio-group>
</view>
</view>
</block>
<block wx:else>
<view class="list-item">
<view class="label">人次:</view>
<view class="label">{{t('userEntityGrant:attr.relationIds')}}</view>
<view class="value">
<l-radio-group
placement="row"
l-class="radio-container"
bind:linchange="setNumberMp"
current="{{userEntityGrant.number}}"
>
<l-radio
wx:key="1"
key="1"
size="48rpx"
l-class="radio"
>
单人次
</l-radio>
<l-radio
wx:key="10000"
key="10000"
size="48rpx"
>
不限人次
</l-radio>
</l-radio-group>
<l-checkbox-group placement="row" bind:linchange="setRelationMp">
<l-checkbox wx:for-items="{{relations}}" wx:key="index" key="{{item.id}}" l-class="radio" size="48rpx" checked="{{item.checked}}">
{{item.display || t(entity + ':r.' + item.name)}}
</l-checkbox>
</l-checkbox-group>
</view>
</view>
</view>
<block wx:if="{{userEntityGrant.relationIds && userEntityGrant.relationIds.length > 1}}">
<view class="list-item">
<view class="label">{{t('userEntityGrant:attr.rule')}}</view>
<view class="value">
<l-radio-group placement="row" l-class="radio-container" bind:linchange="setRuleMp" current="{{userEntityGrant.rule}}">
<l-radio wx:for-items="{{rules}}" wx:key="index" key="{{item}}" size="48rpx">
{{t('userEntityGrant:v.rule.' + item)}}
</l-radio>
</l-radio-group>
</view>
</view>
</block>
<block wx:if="{{ userEntityGrant.type === 'grant' }}">
<view class="list-item">
<view class="label">{{t('multiple')}}</view>
<view class="value">
<l-switch checked="{{ userEntityGrant.multiple || false }}" size="50rpx" bind:linchange="setMultipleMp" />
</view>
</view>
</block>
<view class="list-item">
<view class="label">有效期:</view>
<view class="label">{{t('userEntityGrant:attr.expiresAt')}}</view>
<view class="value-period">
<l-counter
count="{{userEntityGrant.period}}"
max="{{ maxes[unit] }}"
min="1"
round-float="{{true}}"
bind:linchange="setPeriodMp"
/>
<!-- <view style="margin-left: 30rpx">
分钟
</view> -->
<l-counter count="{{period}}" max="{{ maxes[unit] }}" min="1" round-float="{{true}}" bind:linchange="setPeriodMp" />
<picker range="{{unitArr}}" range-key="label" value="{{unitIndex}}" bind:change="setUnitMp">
<view class="unit-box">
<view class="unit">
<view class="unit">
{{unitArr[unitIndex].label}}
</view>
<l-icon l-class="icon" name="down" size="18" />
</view>
<l-icon l-class="icon" name="down" size="18" />
</view>
</picker>
</view>
</view>
<view style="flex: 1" />
<l-button
size="long"
type="default"
bind:lintap="confirm"
>
确定
<l-button size="long" type="default" bind:lintap="confirm">
{{t('common::action.confirm')}}
</l-button>
</block>
</view>

View File

@ -6,7 +6,7 @@ export default function render(props) {
const { relations, userEntityGrant, userEntityGrantId, period, unit, maxes, oakExecutable, rules, } = props.data;
const { relationIds, type, rule, multiple, relationEntity } = userEntityGrant || {};
const { update, t, onBack, confirm, setInit, setPeriod, setUnit } = props.methods;
const P = !!userEntityGrantId ? (_jsxs(_Fragment, { children: [_jsx(Alert, { showIcon: true, message: t('shareCode'), type: "info", style: { marginBottom: 16 } }), _jsx(UserEntityGrantShare, { showBack: false, oakId: userEntityGrantId, oakAutoUnmount: true, oakPath: "$userRelation/upsert/byUserEntityGrant-userEntityGrant/detail" }), _jsx("div", { style: {
const P = !!userEntityGrantId ? (_jsxs(_Fragment, { children: [_jsx(Alert, { showIcon: true, message: t('shareCode'), type: "info", style: { marginBottom: 16 } }), _jsx(UserEntityGrantShare, { oakId: userEntityGrantId, oakAutoUnmount: true, oakPath: "$userRelation/upsert/byUserEntityGrant-userEntityGrant/detail" }), _jsx("div", { style: {
width: '100%',
display: 'flex',
justifyContent: 'flex-end',

Some files were not shown because too many files have changed in this diff Show More