Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions examples/roles-plugin/src/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {notification} from 'antd';

const mg = window.MUSE_GLOBAL;

// 根据环境是否本地判断API服务器地址
export const apiHost = mg.isLocal ? window.MUSE_GLOBAL.appVariables?.museRunnerApiHost || 'localhost:6066' : document.location.host;

// 构建api基准地址,后续请求都从这里发起
const baseURL = `http://${apiHost}/api`;

// 定义通用异步请求函数request
async function request(path, options={})
{
const url = /^https?:\/\//.test(path) ? path : baseURL + path;

let response;

// 发起fetch网络请求
try{
response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...(options.headers || {}),
},
...options,
body: options.body && typeof options.body !== 'string' ? JSON.stringify(options.body) : options.body,
});
} catch (err) {
throw err;
}

// 解析响应体为json数据
const data = await response.json().catch(() => ({}));

// 若后端返回server错误
if (response.status === 500) {
notification.error ({
message : 'Server Error',
description : data
});
throw new Error (data);
}
return {...response, data};
}


// 对外暴露常用的http请求方式
const apiClient = {
get:
(path,options)=>request(path,{...options,method:'GET'}),
post:
(path,body,options)=>request(path,{...options,method:'POST',body}),
put:
(path,body,options)=>request(path,{...options,method:'PUT',body}),
delete:
(path,options)=>request(path,{...options,method:'DELETE'}),
baseURL,
};

export default apiClient;
33 changes: 15 additions & 18 deletions examples/roles-plugin/src/components/RoleInfoModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,38 @@ import { Form, Modal } from 'antd';
import FormBuilder from '@ebay/nice-form-react';
import { useDispatch } from 'react-redux';
import NiceModal, { useModal, antdModalV5 } from '@ebay/nice-modal-react';
import { useAddRole, useEditRole } from '../hooks/useRoleMutation';

const RoleInfoModal = NiceModal.create(({ role }) => {
const dispatch = useDispatch();
const modal = useModal();
const [form] = Form.useForm();
const meta = {
initialValues: role,
initialValues: role,
fields: [
{ key: 'name', label: 'Name', required: true },
{ key: 'description', label: 'Description', widget: 'textarea', required: true },
{ key: 'duty', label: 'Duty', widget: 'textarea', required: true },
{ key: 'roleLevel', label: 'Role Level', widget: 'textarea', required: true },
],
};

const addRoleMutation = useAddRole();
const editRoleMutation = useEditRole();

const handleSubmit = useCallback(() => {
form.validateFields().then(() => {
const newRole = { ...form.getFieldsValue() };
form.validateFields().then(async () => {
const formData = form.getFieldsValue();
let result;
if (!role) {
// Create a new role
dispatch({
type: 'new-role',
payload: newRole,
});
result = await addRoleMutation.mutateAsync(formData);
} else {
// Update a role
newRole.id = role.id;
dispatch({
type: 'update-role',
payload: newRole,
});
result = await editRoleMutation.mutateAsync({ ...formData, id: role.id });
}

modal.resolve(newRole);
modal.resolve(result);
modal.hide();
});
}, [modal, role, dispatch, form]);
}, [modal, role, form, addRoleMutation, editRoleMutation]);

return (
<Modal
{...antdModalV5(modal)}
Expand Down
5 changes: 3 additions & 2 deletions examples/roles-plugin/src/components/RoleList.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMemo, useCallback, useState } from 'react';
import { Button, Table } from 'antd';
import { Button, Table, Spin } from 'antd';
import { EditOutlined } from '@ant-design/icons';
import jsPlugin from 'js-plugin';
import _ from 'lodash';
Expand All @@ -8,10 +8,11 @@ import { useSelector } from 'react-redux';
import { useModal } from '@ebay/nice-modal-react';
import RoleInfoModal from './RoleInfoModal';
import './RoleList.less';
import useRoles from '../hooks/useRoles';

export default function RoleList() {
const roleModal = useModal(RoleInfoModal);
const roles = useSelector((s) => s.pluginRolesPlugin.roles);
const { data: roles = [], isLoading, error } = useRoles();

const columns = useMemo(
() => [
Expand Down
60 changes: 60 additions & 0 deletions examples/roles-plugin/src/hooks/useRoleMutation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

// 从本地缓存读取roles key的值;如果有值就从字符串json解析为数组返回
function getRolesFromLocal() {
const local = localStorage.getItem('roles');
return local ? JSON.parse(local) : [];
}

// 传入的roles数组转为字符串,存储到localstorage的roles key
function saveRolesToLocal(roles) {
localStorage.setItem('roles', JSON.stringify(roles));
}

// 添加新角色
export function useAddRole() {
const queryClient = useQueryClient();
// 添加角色的异步操作方法
return useMutation({
// 要添加的新角色数据对象
mutationFn: async (role) => {
// 获取本地角色列表
const roles = getRolesFromLocal();
// 计算已有角色最大id
const maxId = roles.length ? Math.max(...roles.map(r => Number(r.id))) : 0;
// 新角色分配id
const newRole = { ...role, id: maxId + 1 };
// 新角色数组
const updatedRoles = [...roles, newRole];
// 存入本地
saveRolesToLocal(updatedRoles);
return newRole;
},
onSuccess: () => {
// 刷新
queryClient.invalidateQueries(['roles']);
}
});
}

// 编辑角色
export function useEditRole() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (role) => {
const roles = getRolesFromLocal();
const idx = roles.findIndex(r => String(r.id) === String(role.id));
if (idx !== -1) {
roles[idx] = { ...roles[idx], ...role };
saveRolesToLocal(roles);
}
return role;
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries(['roles']);
if (variables && variables.id) {
queryClient.invalidateQueries(['roleDetail', String(variables.id)]);
}
}
});
}
35 changes: 35 additions & 0 deletions examples/roles-plugin/src/hooks/useRoles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useQuery } from '@tanstack/react-query';
import apiClient from '../api';

const ROLES_URL = "https://cors-anywhere.herokuapp.com/https://lechang97.github.io/muse-next-database/mock/roles.json";

function getRolesFromLocal() {
const local = localStorage.getItem('roles');
return local ? JSON.parse(local) : null;
}

function saveRolesToLocal(roles) {
localStorage.setItem('roles', JSON.stringify(roles));
}

export default function useRoles() {
return useQuery({
queryKey: ['roles'],
queryFn: async () => {
const res = await apiClient.get(ROLES_URL);
const remoteRoles = res.data || [];
const localRoles = getRolesFromLocal() || [];
const merged = [
...remoteRoles.map(remoteRole => {
const local = localRoles.find(localRole => String(localRole.id) === String(remoteRole.id));
return local ? { ...remoteRole, ...local } : remoteRole;
}),
...localRoles.filter(
localRole => !remoteRoles.some(remoteRole => String(remoteRole.id) === String(localRole.id))
),
];
saveRolesToLocal(merged);
return merged;
},
});
}
53 changes: 53 additions & 0 deletions examples/users-plugin/src/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {notification} from 'antd';

const mg = window.MUSE_GLOBAL;

export const apiHost = mg.isLocal ? window.MUSE_GLOBAL.appVariables?.museRunnerApiHost || 'localhost:6066' : document.location.host;

const baseURL = `http://${apiHost}/api`;

async function request(path, options={})
{
const url = /^https?:\/\//.test(path) ? path : baseURL + path;

let response;

try{
response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...(options.headers || {}),
},
...options,
body: options.body && typeof options.body !== 'string' ? JSON.stringify(options.body) : options.body,
});
} catch (err) {
throw err;
}

const data = await response.json().catch(() => ({}));

if (response.status === 500) {
notification.error ({
message : 'Server Error',
description : data
});
throw new Error (data);
}
return {...response, data};
}


const apiClient = {
get:
(path,options)=>request(path,{...options,method:'GET'}),
post:
(path,body,options)=>request(path,{...options,method:'POST',body}),
put:
(path,body,options)=>request(path,{...options,method:'PUT',body}),
delete:
(path,options)=>request(path,{...options,method:'DELETE'}),
baseURL,
};

export default apiClient;
23 changes: 13 additions & 10 deletions examples/users-plugin/src/components/UserDetail.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { useParams } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { Button, Avatar, Form, Alert, Tabs } from 'antd';
import NiceModal from '@ebay/nice-modal-react';
import UserInfoModal from './UserInfoModal';
import NiceForm from '@ebay/nice-form-react';
import utils from '@ebay/muse-lib-antd/src/utils';
import { extendArray } from '@ebay/muse-lib-antd/src/utils';
import { Children } from 'react';
import useUserDetail from '../hooks/useUserDetail';
import useAvatar from '../hooks/useAvatar';

export default function UserDetail() {
const { id } = useParams();
const user = useSelector(state =>
state.pluginUsersPlugin.users.find(u => u.id === parseInt(id))
);
const { data: user, isLoading, isError } = useUserDetail(id);
const avatarUrl = useAvatar(user);

if (!user) {
return <Alert message="The user does not exist." type="error" showIcon />;
if (isLoading) {
return <Alert message="Loading..." type="info" showIcon />;
}
if (isError || !user) {
return <Alert message="User not found" type="error" showIcon />;
}

const meta = {
Expand Down Expand Up @@ -45,16 +47,17 @@ export default function UserDetail() {
}
];
extendArray(tabs, 'tabs', 'userDetailTab', { tabs, user });
console.log('extended tabs:', tabs);

return (
<div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 24 }}>
<Avatar
src={user.avatar}
src={avatarUrl}
size={64}
style={{ marginRight: 16, fontSize: 28 }}
/>
>
{!avatarUrl && user.name ? user.name[0] : null}
</Avatar>
<span style={{ fontSize: 28, fontWeight: 600 }}>{user.name}</span>
<Button
type="link"
Expand Down
Loading