message页面和一些其它页面
This commit is contained in:
parent
12728fc81a
commit
4e135506f9
|
|
@ -1,135 +1,57 @@
|
|||
import { concat, without } from "lodash";
|
||||
import { NotificationData } from "oak-frontend-base";
|
||||
|
||||
interface TimedNotificationdata extends NotificationData {
|
||||
dieAt: number;
|
||||
};
|
||||
|
||||
let KILLER: number | undefined = undefined;
|
||||
export default OakComponent({
|
||||
options: {
|
||||
multipleSlots: true,
|
||||
},
|
||||
externalClasses: ['l-class', 'l-image-class', 'l-lass-image'],
|
||||
properties: {
|
||||
zIndex: {
|
||||
type: Number,
|
||||
value: 777,
|
||||
},
|
||||
show: Boolean,
|
||||
icon: String,
|
||||
iconColor: {
|
||||
type: String,
|
||||
value: '#fff',
|
||||
},
|
||||
iconSize: {
|
||||
type: String,
|
||||
value: '28',
|
||||
},
|
||||
image: String,
|
||||
content: String,
|
||||
type: {
|
||||
type: String,
|
||||
value: 'info',
|
||||
options: ['info', 'warning', 'success', 'error', 'loading'],
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
value: 1500,
|
||||
},
|
||||
openApi: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
/**
|
||||
* message距离顶部的距离
|
||||
*/
|
||||
top: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
status: false,
|
||||
messages: [] as TimedNotificationdata[],
|
||||
},
|
||||
async formData({ }) {
|
||||
const data = this.consumeNotification();
|
||||
if (data) {
|
||||
const now = Date.now();
|
||||
return {
|
||||
messages: [
|
||||
...this.state.messages,
|
||||
Object.assign(data, {
|
||||
dieAt: now + (data.duration || 3000),
|
||||
})
|
||||
],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
// 解决 addListener undefined 的错误
|
||||
observers: {
|
||||
show: function (show) {
|
||||
show && this.changeStatus();
|
||||
if (!show)
|
||||
this.setData({
|
||||
status: show,
|
||||
});
|
||||
},
|
||||
},
|
||||
messages(messages: TimedNotificationdata[]) {
|
||||
if (messages.length > 0) {
|
||||
let firstDieAt: number = Number.MAX_VALUE;
|
||||
let vicitim: TimedNotificationdata;
|
||||
for (const message of messages) {
|
||||
if (message.dieAt < firstDieAt) {
|
||||
vicitim = message;
|
||||
firstDieAt = vicitim.dieAt;
|
||||
}
|
||||
}
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.initMessage();
|
||||
},
|
||||
},
|
||||
|
||||
pageLifetimes: {
|
||||
show() {
|
||||
this.initMessage();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeStatus() {
|
||||
this.setState({
|
||||
status: true,
|
||||
});
|
||||
// @ts-ignore
|
||||
if (this.data.timer) clearTimeout(this.data.timer);
|
||||
// @ts-ignore
|
||||
this.data.timer = setTimeout(() => {
|
||||
this.setState({
|
||||
status: false,
|
||||
});
|
||||
// @ts-ignore
|
||||
if (this.data.success) this.data.success();
|
||||
// @ts-ignore
|
||||
this.data.timer = null;
|
||||
}, this.data.duration);
|
||||
},
|
||||
initMessage() {
|
||||
let oak;
|
||||
// 小程序有wx、web有window
|
||||
if (process.env.OAK_PLATFORM === 'wechatMp') {
|
||||
// @ts-ignore
|
||||
oak = wx.oak || {};
|
||||
} else {
|
||||
// @ts-ignore
|
||||
oak = window.oak || {};
|
||||
if (typeof KILLER === 'number') {
|
||||
clearTimeout(KILLER);
|
||||
}
|
||||
KILLER = setTimeout(
|
||||
() => {
|
||||
const messages = without(this.state.messages, vicitim);
|
||||
this.setState({
|
||||
messages,
|
||||
});
|
||||
}
|
||||
, Math.max(firstDieAt - Date.now(), 0));
|
||||
}
|
||||
oak.showMessage = (options: {
|
||||
content: string;
|
||||
image: string;
|
||||
type: string;
|
||||
duration: number;
|
||||
success: any;
|
||||
top: number;
|
||||
}) => {
|
||||
const {
|
||||
content = '',
|
||||
image = '',
|
||||
type = 'info',
|
||||
duration = 1500,
|
||||
success = null,
|
||||
top = 0,
|
||||
} = options;
|
||||
this.data.success = success;
|
||||
this.setState({
|
||||
// @ts-ignore
|
||||
content,
|
||||
image,
|
||||
duration,
|
||||
type,
|
||||
top,
|
||||
});
|
||||
this.changeStatus();
|
||||
return this;
|
||||
};
|
||||
oak.hideMessage = () => {
|
||||
this.setState({
|
||||
status: false,
|
||||
});
|
||||
};
|
||||
},
|
||||
},
|
||||
else {
|
||||
KILLER = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,61 +16,60 @@ const typeToIcon = {
|
|||
};
|
||||
|
||||
export default function render() {
|
||||
console.log('message render');
|
||||
const {
|
||||
type,
|
||||
content,
|
||||
image,
|
||||
zIndex = 777,
|
||||
top = 100,
|
||||
icon,
|
||||
iconColor = '#fff',
|
||||
iconSize = 16,
|
||||
show,
|
||||
} = this.props;
|
||||
const { status } = this.state;
|
||||
return (
|
||||
<div
|
||||
className={classNames('l-message', 'l-class', {
|
||||
[`l-message-${type}`]: type,
|
||||
'l-message-show': status,
|
||||
})}
|
||||
style={{
|
||||
zIndex: zIndex,
|
||||
top: `${top}px`,
|
||||
}}
|
||||
>
|
||||
{status && (
|
||||
<React.Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginRight: '15rpx',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name={type}
|
||||
size={iconSize}
|
||||
color={type === 'warning' ? '#333' : iconColor}
|
||||
/>
|
||||
</div>
|
||||
{image && (
|
||||
<img
|
||||
src={image}
|
||||
className={classNames(
|
||||
'l-message-image',
|
||||
'l-class-image',
|
||||
'l-image-class'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{content}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
messages,
|
||||
} = this.state;
|
||||
if (messages.length > 0) {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
messages.map(
|
||||
(ele, index) => {
|
||||
const {
|
||||
type,
|
||||
content,
|
||||
icon,
|
||||
iconColor = '#fff',
|
||||
iconSize = 16
|
||||
} = ele;
|
||||
return (
|
||||
<div
|
||||
className={classNames('l-message', 'l-message-show', {
|
||||
[`l-message-${type}`]: type,
|
||||
})}
|
||||
style={{
|
||||
zIndex: 777,
|
||||
top: 31 * index,
|
||||
}}
|
||||
>
|
||||
<React.Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginRight: '15rpx',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name={type}
|
||||
size={iconSize}
|
||||
color={type === 'warning' ? '#333' : iconColor}
|
||||
/>
|
||||
</div>
|
||||
{content}
|
||||
</React.Fragment>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function Icon({ name, size, color }) {
|
||||
const I = typeToIcon[name]
|
||||
|
||||
|
||||
return <I style={{ fontSize: `${size}px`, color }} />;
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import { composeFileUrl } from '../../../../src/utils/extraFile';
|
|||
const SEND_KEY = 'captcha:sendAt';
|
||||
export default OakPage({
|
||||
path: 'mobile:me',
|
||||
entity: 'mobile',
|
||||
isList: false,
|
||||
projection: {
|
||||
id: 1,
|
||||
mobile: 1,
|
||||
|
|
@ -15,6 +15,10 @@ export default OakPage({
|
|||
captcha: '',
|
||||
counter: 0,
|
||||
},
|
||||
properties: {
|
||||
onlyCaptcha: Boolean,
|
||||
onlyPassword: Boolean,
|
||||
},
|
||||
async formData({ features }) {
|
||||
const lastSendAt = features.localStorage.load(SEND_KEY);
|
||||
const now = Date.now();
|
||||
|
|
|
|||
|
|
@ -7,12 +7,157 @@ import { isMobile, isPassword, isCaptcha } from 'oak-domain/lib/utils/validator'
|
|||
const { TabPane } = Tabs;
|
||||
|
||||
export default function render() {
|
||||
const { mobile, captcha, password, counter} = this.state;
|
||||
const { onlyCaptcha, onlyPassword } = this.props;
|
||||
const { mobile, captcha, password, counter } = this.state;
|
||||
const validMobile = isMobile(mobile);
|
||||
const validCaptcha = isCaptcha(captcha);
|
||||
const validPassword = isPassword(password);
|
||||
const allowSubmit = validMobile && (validCaptcha|| validPassword);
|
||||
const allowSubmit = validMobile && (validCaptcha || validPassword);
|
||||
|
||||
const LoginPassword = (
|
||||
<Form
|
||||
name="normal_login"
|
||||
className="login-form"
|
||||
initialValues={{ remember: true }}
|
||||
>
|
||||
<Form.Item
|
||||
name="mobile"
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
value={mobile}
|
||||
type="tel"
|
||||
data-attr="mobile"
|
||||
maxLength={11}
|
||||
prefix={<MobileOutlined className="site-form-item-icon" />}
|
||||
placeholder="Mobile"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
value={password}
|
||||
data-attr="password"
|
||||
prefix={<LockOutlined className="site-form-item-icon" />}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="login-form-button"
|
||||
disabled={!allowSubmit}
|
||||
onClick={() => this.loginByMobile()}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
const LoginCaptcha = (
|
||||
<Form
|
||||
name="normal_login"
|
||||
className="login-form"
|
||||
initialValues={{ remember: true }}
|
||||
>
|
||||
<Form.Item
|
||||
name="mobile"
|
||||
>
|
||||
<Input.Group compact>
|
||||
<Input
|
||||
allowClear
|
||||
value={mobile}
|
||||
data-attr="mobile"
|
||||
type="tel"
|
||||
maxLength={11}
|
||||
prefix={<MobileOutlined className="site-form-item-icon" />}
|
||||
placeholder="Mobile"
|
||||
style={{ width: 'calc(100% - 65px)' }}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{
|
||||
width: 65,
|
||||
}}
|
||||
disabled={!validMobile || counter > 0}
|
||||
onClick={() => this.sendCaptcha()}
|
||||
>
|
||||
{counter > 0 ? counter : 'Send'}
|
||||
</Button>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="captcha"
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
value={captcha}
|
||||
data-attr="captcha"
|
||||
prefix={<FieldNumberOutlined className="site-form-item-icon" />}
|
||||
type="number"
|
||||
maxLength={4}
|
||||
placeholder="Captcha"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="login-form-button"
|
||||
disabled={!allowSubmit}
|
||||
onClick={() => this.loginByMobile()}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
if (onlyCaptcha) {
|
||||
return (
|
||||
<div className='page-body'>
|
||||
<div style={{
|
||||
flex: 2,
|
||||
}} />
|
||||
{LoginCaptcha}
|
||||
<div style={{
|
||||
flex: 3,
|
||||
}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if (onlyPassword) {
|
||||
return (
|
||||
<div className='page-body'>
|
||||
<div style={{
|
||||
flex: 2,
|
||||
}} />
|
||||
{LoginPassword}
|
||||
<div style={{
|
||||
flex: 3,
|
||||
}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className='page-body'>
|
||||
<div style={{
|
||||
|
|
@ -21,122 +166,10 @@ export default function render() {
|
|||
<Card className="card">
|
||||
<Tabs defaultActiveKey="1" size="large" tabBarStyle={{ width: '100%' }}>
|
||||
<TabPane tab="in Password" key="1">
|
||||
<Form
|
||||
name="normal_login"
|
||||
className="login-form"
|
||||
initialValues={{ remember: true }}
|
||||
>
|
||||
<Form.Item
|
||||
name="mobile"
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
value={mobile}
|
||||
type="tel"
|
||||
data-attr="mobile"
|
||||
maxLength={11}
|
||||
prefix={<MobileOutlined className="site-form-item-icon" />}
|
||||
placeholder="Mobile"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
value={password}
|
||||
data-attr="password"
|
||||
prefix={<LockOutlined className="site-form-item-icon" />}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="login-form-button"
|
||||
disabled={!allowSubmit}
|
||||
onClick={() => this.loginByMobile()}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{LoginPassword}
|
||||
</TabPane>
|
||||
<TabPane tab="in Captcha" key="2">
|
||||
<Form
|
||||
name="normal_login"
|
||||
className="login-form"
|
||||
initialValues={{ remember: true }}
|
||||
>
|
||||
<Form.Item
|
||||
name="mobile"
|
||||
>
|
||||
<Input.Group compact>
|
||||
<Input
|
||||
allowClear
|
||||
value={mobile}
|
||||
data-attr="mobile"
|
||||
type="tel"
|
||||
maxLength={11}
|
||||
prefix={<MobileOutlined className="site-form-item-icon" />}
|
||||
placeholder="Mobile"
|
||||
style={{ width: 'calc(100% - 65px)' }}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{
|
||||
width:65,
|
||||
}}
|
||||
disabled={!validMobile || counter > 0}
|
||||
onClick={() => this.sendCaptcha()}
|
||||
>
|
||||
{counter > 0 ? counter : 'Send'}
|
||||
</Button>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="captcha"
|
||||
>
|
||||
<Input
|
||||
allowClear
|
||||
value={captcha}
|
||||
data-attr="captcha"
|
||||
prefix={<FieldNumberOutlined className="site-form-item-icon" />}
|
||||
type="number"
|
||||
maxLength={4}
|
||||
placeholder="Captcha"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.onInput(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="login-form-button"
|
||||
disabled={!allowSubmit}
|
||||
onClick={() => this.loginByMobile()}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{LoginCaptcha}
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
flex: 1;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
align-items: stretch;
|
||||
background-color: @background-color-base;
|
||||
.safe-area-inset-bottom();
|
||||
}
|
||||
|
|
@ -23,6 +24,12 @@
|
|||
padding: @size-spacing-base;
|
||||
margin: @size-spacing-base;
|
||||
margin-bottom: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
text {
|
||||
font-size: x-large;
|
||||
}
|
||||
}
|
||||
.btn-box {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ export default OakPage({
|
|||
formData: async ({ data: mobiles }) => ({
|
||||
mobiles,
|
||||
}),
|
||||
data: {
|
||||
confirmDeleteModalVisible: false,
|
||||
refreshing: false,
|
||||
deleteIdx: undefined,
|
||||
},
|
||||
methods: {
|
||||
async onRefreshMobile(e: any) {
|
||||
this.setState({
|
||||
|
|
@ -47,5 +52,17 @@ export default OakPage({
|
|||
refreshing: false,
|
||||
});
|
||||
},
|
||||
|
||||
goAddMobile() {
|
||||
const eventLoggedIn = `mobile:me:login:${Date.now()}`;
|
||||
this.sub(eventLoggedIn, () => {
|
||||
this.navigateBack();
|
||||
})
|
||||
this.navigateTo({
|
||||
url: '/mobile/login',
|
||||
onlyCaptcha: true,
|
||||
eventLoggedIn,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -1,9 +1,61 @@
|
|||
import React, { Component } from 'react';
|
||||
import { List, Modal, Typography, Button } from 'antd';
|
||||
const { Title } = Typography;
|
||||
import { PhoneOutlined, MinusOutlined } from '@ant-design/icons';
|
||||
|
||||
export default function render() {
|
||||
const { mobiles, confirmDeleteModalVisible, deleteIdx } = this.state;
|
||||
return (
|
||||
<div>
|
||||
react
|
||||
<div className='page-body'>
|
||||
<List
|
||||
grid={{ gutter: 16, column: 1 }}
|
||||
itemLayout="horizontal"
|
||||
dataSource={mobiles}
|
||||
renderItem={(item, idx) => (
|
||||
<List.Item>
|
||||
<div className='card'>
|
||||
<text>{item.mobile}</text>
|
||||
<Button
|
||||
type='primary'
|
||||
shape='circle'
|
||||
icon={<MinusOutlined style={{ fontSize: 20 }} />}
|
||||
size="large"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
confirmDeleteModalVisible: true,
|
||||
deleteIdx: idx,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
<div style={{ flex: 1 }} />
|
||||
<div className='btn-box'>
|
||||
<Button size='large' type="primary" block onClick={() => this.goAddMobile()}>
|
||||
增加
|
||||
</Button>
|
||||
</div>
|
||||
<Modal
|
||||
closable={false}
|
||||
visible={confirmDeleteModalVisible}
|
||||
onOk={async () => {
|
||||
this.execute('remove', undefined, `${deleteIdx}`);
|
||||
this.setState({
|
||||
confirmDeleteModalVisible: false,
|
||||
deleteIdx: undefined,
|
||||
});
|
||||
}}
|
||||
onCancel={() => this.setState({
|
||||
confirmDeleteModalVisible: false,
|
||||
deleteIdx: undefined,
|
||||
})}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
>
|
||||
确认删除手机号吗?
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,8 +56,13 @@ export default OakPage({
|
|||
filters: [{
|
||||
filter: async ({ features }) => {
|
||||
const tokenId = await features.token.getToken();
|
||||
if (tokenId) {
|
||||
return {
|
||||
id: tokenId,
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: tokenId,
|
||||
id: 'none',
|
||||
};
|
||||
},
|
||||
}],
|
||||
|
|
@ -89,8 +94,13 @@ export default OakPage({
|
|||
},
|
||||
data: {
|
||||
refreshing: false,
|
||||
showDrawer: false,
|
||||
},
|
||||
methods: {
|
||||
setValue(input: any) {
|
||||
const { dataset, value } = this.resolveInput(input);
|
||||
this.setUpdateData(dataset!.attr, value);
|
||||
},
|
||||
async onRefresh() {
|
||||
this.setState({
|
||||
refreshing: true,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import React, { Component } from 'react';
|
||||
import { UserOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Image, Button, List } from 'antd';
|
||||
import { Avatar, Image, Button, List, Drawer, Input } from 'antd';
|
||||
|
||||
export default function render() {
|
||||
const { avatar, nickname, isLoggedIn, refreshing, mobile, mobileCount } = this.state;
|
||||
const { avatar, nickname, isLoggedIn, refreshing, mobile, mobileCount, showDrawer, oakDirty, bbb } = this.state;
|
||||
const mobileText = mobileCount > 1 ? `${mobileCount}条手机号` : ( mobile || '未设置');
|
||||
return (
|
||||
<div className='page-body'>
|
||||
|
|
@ -18,7 +18,9 @@ export default function render() {
|
|||
size="small"
|
||||
disabled={refreshing}
|
||||
loading={refreshing}
|
||||
onClick={() => this.onRefresh()}
|
||||
onClick={() => this.setState({
|
||||
showDrawer: true,
|
||||
})}
|
||||
>
|
||||
更新
|
||||
</Button> :
|
||||
|
|
@ -41,12 +43,48 @@ export default function render() {
|
|||
<List.Item.Meta
|
||||
title="手机号"
|
||||
description={mobileText}
|
||||
onClick={() => console.log('aaa')}
|
||||
onClick={() => this.goMyMobile()}
|
||||
/>
|
||||
<RightOutlined />
|
||||
</List.Item>
|
||||
</List>
|
||||
</div>
|
||||
<Drawer
|
||||
height={150}
|
||||
closable={false}
|
||||
placement="bottom"
|
||||
visible={showDrawer}
|
||||
onClose={() => {
|
||||
this.setState({ showDrawer: false });
|
||||
this.resetUpdateData();
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
size="large"
|
||||
placeholder="请输入昵称"
|
||||
value={bbb}
|
||||
onChange={(input) => {
|
||||
console.log(input.currentTarget.value);
|
||||
this.setState({
|
||||
bbb: input.currentTarget.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div style={{ height: 15 }} />
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
disabled={!oakDirty}
|
||||
block
|
||||
onClick={async () => {
|
||||
await this.execute('update', undefined, '0.user');
|
||||
this.setState({ showDrawer: false });
|
||||
this.resetUpdateData();
|
||||
}}
|
||||
>
|
||||
{this.t('common:confirm')}
|
||||
</Button>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { EntityDict } from 'general-app-domain';
|
||||
import { AppType } from 'general-app-domain/Application/Schema';
|
||||
import { Action, Feature } from 'oak-frontend-base';
|
||||
import { Action, Feature, LocalStorage } from 'oak-frontend-base';
|
||||
import { Aspect, AspectWrapper, Context, SelectRowShape } from 'oak-domain/lib/types';
|
||||
import { RWLock } from 'oak-domain/lib/utils/concurrent';
|
||||
import { Cache } from 'oak-frontend-base';
|
||||
|
|
@ -39,27 +39,33 @@ export class Application<ED extends EntityDict, Cxt extends GeneralRuntimeContex
|
|||
private application?: SelectRowShape<ED['application']['Schema'], typeof projection>;
|
||||
private rwLock: RWLock;
|
||||
private cache: Cache<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>;
|
||||
private storage: LocalStorage<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>;
|
||||
|
||||
constructor(
|
||||
aspectWrapper: AspectWrapper<ED, Cxt, AD>,
|
||||
type: AppType,
|
||||
url: string,
|
||||
cache: Cache<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>,
|
||||
storage: LocalStorage<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>,
|
||||
callback: (application: SelectRowShape<ED['application']['Schema'], typeof projection>) => void) {
|
||||
super(aspectWrapper);
|
||||
this.rwLock = new RWLock();
|
||||
this.cache = cache;
|
||||
this.refresh(type, url, callback);
|
||||
this.storage = storage;
|
||||
const applicationId = storage.load('application:applicationId');
|
||||
this.rwLock.acquire('X');
|
||||
if (applicationId) {
|
||||
this.applicationId = applicationId;
|
||||
this.getApplicationFromCache(callback);
|
||||
}
|
||||
else {
|
||||
this.refresh(type, url, callback);
|
||||
}
|
||||
this.rwLock.release();
|
||||
}
|
||||
|
||||
private async refresh(type: AppType, url: string, callback: (application: SelectRowShape<ED['application']['Schema'], typeof projection>) => void) {
|
||||
this.rwLock.acquire('X');
|
||||
const { result: applicationId } = await this.getAspectWrapper().exec('getApplication', {
|
||||
url,
|
||||
type,
|
||||
});
|
||||
this.applicationId = applicationId;
|
||||
const result = await this.cache.get('application', {
|
||||
private async getApplicationFromCache(callback: (application: SelectRowShape<ED['application']['Schema'], typeof projection>) => void) {
|
||||
const { result } = await this.cache.refresh('application', {
|
||||
data: projection,
|
||||
filter: {
|
||||
id: this.applicationId,
|
||||
|
|
@ -67,10 +73,19 @@ export class Application<ED extends EntityDict, Cxt extends GeneralRuntimeContex
|
|||
});
|
||||
assert(result.length === 1);
|
||||
this.application = result[0] as any;
|
||||
this.rwLock.release();
|
||||
callback(this.application!);
|
||||
}
|
||||
|
||||
private async refresh(type: AppType, url: string, callback: (application: SelectRowShape<ED['application']['Schema'], typeof projection>) => void) {
|
||||
const { result: applicationId } = await this.getAspectWrapper().exec('getApplication', {
|
||||
url,
|
||||
type,
|
||||
});
|
||||
this.applicationId = applicationId;
|
||||
this.storage.save('application:applicationId', applicationId);
|
||||
this.getApplicationFromCache(callback);
|
||||
}
|
||||
|
||||
async getApplication() {
|
||||
this.rwLock.acquire('S');
|
||||
const result = this.application!;
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export function initialize<ED extends EntityDict, Cxt extends GeneralRuntimeCont
|
|||
context: Cxt,
|
||||
) {
|
||||
const application = new Application<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>(
|
||||
aspectWrapper, type, url, basicFeatures.cache, (application) => context.setApplication(application));
|
||||
const token = new Token<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>(aspectWrapper, basicFeatures.cache, context);
|
||||
aspectWrapper, type, url, basicFeatures.cache, basicFeatures.localStorage, (application) => context.setApplication(application));
|
||||
const token = new Token<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>(aspectWrapper, basicFeatures.cache, basicFeatures.localStorage, context);
|
||||
const extraFile = new ExtraFile<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>(aspectWrapper);
|
||||
return {
|
||||
token,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { EntityDict } from 'general-app-domain';
|
||||
import { Action, Feature } from 'oak-frontend-base';
|
||||
import { Action, Feature, LocalStorage } from 'oak-frontend-base';
|
||||
import { RWLock } from 'oak-domain/lib/utils/concurrent';
|
||||
import { WebEnv, WechatMpEnv } from 'general-app-domain/Token/Schema';
|
||||
import { Cache } from 'oak-frontend-base';
|
||||
|
|
@ -15,12 +15,20 @@ export class Token<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>,
|
|||
private rwLock: RWLock;
|
||||
private cache: Cache<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>;
|
||||
private context: Cxt;
|
||||
private storage: LocalStorage<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>;
|
||||
|
||||
constructor(aspectWrapper: AspectWrapper<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>, cache: Cache<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>, context: Cxt) {
|
||||
constructor(aspectWrapper: AspectWrapper<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>,
|
||||
cache: Cache<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>, storage: LocalStorage<ED, Cxt, AD & CommonAspectDict<ED, Cxt>>, context: Cxt) {
|
||||
super(aspectWrapper);
|
||||
this.rwLock = new RWLock();
|
||||
this.cache = cache;
|
||||
this.context = context;
|
||||
this.storage = storage;
|
||||
const token = storage.load('token:token');
|
||||
if (token) {
|
||||
this.token = token;
|
||||
this.context.setToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Action
|
||||
|
|
@ -31,6 +39,7 @@ export class Token<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>,
|
|||
const { result } = await this.getAspectWrapper().exec('loginByMobile', { password, mobile, captcha, env });
|
||||
this.token = result;
|
||||
this.rwLock.release();
|
||||
this.storage.save('token:token', result);
|
||||
this.context.setToken(result);
|
||||
}
|
||||
catch (err) {
|
||||
|
|
@ -51,8 +60,9 @@ export class Token<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>,
|
|||
env: env as WechatMpEnv,
|
||||
});
|
||||
this.token = result;
|
||||
this.context.setToken(result);
|
||||
this.rwLock.release();
|
||||
this.storage.save('token:token', result);
|
||||
this.context.setToken(result);
|
||||
}
|
||||
catch(err) {
|
||||
this.rwLock.release();
|
||||
|
|
@ -81,6 +91,7 @@ export class Token<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>,
|
|||
async logout() {
|
||||
this.token = undefined;
|
||||
this.context.setToken(undefined);
|
||||
this.storage.remove('token:token');
|
||||
}
|
||||
|
||||
async getToken() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1 @@
|
|||
import { v1 } from 'uuid';
|
||||
|
||||
let iter = 0;
|
||||
|
||||
while( iter ++ < 20) {
|
||||
console.log(v1());
|
||||
}
|
||||
console.log(undefined === undefined);
|
||||
Loading…
Reference in New Issue