Browse Source

feat: 应用配置编辑

lihao
nili 7 months ago
parent
commit
c64065b144
  1. 90
      dist/247.3fe8fb10.async.js
  2. 6
      dist/335.a2e08496.async.js
  3. 21
      dist/417.ef345f0b.async.js
  4. 66
      dist/531.7b55d7f6.async.js
  5. 2
      dist/635.477947e4.async.js
  6. 70
      dist/886.6c2624b2.async.js
  7. 2
      dist/930.fca7adbf.async.js
  8. 4
      dist/index.html
  9. 1
      dist/p__AppManagement.94b1a1be.async.js
  10. 1
      dist/p__App__AppManagement.c0407dd6.async.js
  11. 196
      dist/umi.17046495.js
  12. 217
      src/pages/App/AliPayConfigForm.tsx
  13. 96
      src/pages/App/AppBasicConfigForm.tsx
  14. 252
      src/pages/App/AppManagement.tsx
  15. 88
      src/pages/App/CashConfigForm.tsx
  16. 235
      src/pages/App/SuperAdminAppManagement.tsx
  17. 62
      src/pages/App/UMengConfigForm.tsx
  18. 57
      src/pages/App/WxConfigForm.tsx
  19. 15
      src/services/matrix/admin.ts
  20. 4
      src/services/matrix/index.ts
  21. 170
      src/services/matrix/matrixAppConfigController.ts
  22. 38
      src/services/matrix/matrixAppController.ts
  23. 98
      src/services/matrix/typings.d.ts
  24. 15
      src/services/matrix/userController.ts

90
dist/247.3fe8fb10.async.js

File diff suppressed because one or more lines are too long

6
dist/335.e3ecf74a.async.js → dist/335.a2e08496.async.js

File diff suppressed because one or more lines are too long

21
dist/417.ef345f0b.async.js

File diff suppressed because one or more lines are too long

66
dist/531.5028b00b.async.js → dist/531.7b55d7f6.async.js

File diff suppressed because one or more lines are too long

2
dist/635.84db70b9.async.js → dist/635.477947e4.async.js

File diff suppressed because one or more lines are too long

70
dist/886.6c2624b2.async.js

File diff suppressed because one or more lines are too long

2
dist/930.42e2019f.async.js → dist/930.fca7adbf.async.js

File diff suppressed because one or more lines are too long

4
dist/index.html

@ -7,10 +7,10 @@
<title>Ant Design Pro</title> <title>Ant Design Pro</title>
<link rel="stylesheet" href="/umi.1ca9308c.css"> <link rel="stylesheet" href="/umi.1ca9308c.css">
<script async src="/scripts/loading.js"></script> <script async src="/scripts/loading.js"></script>
<script>!function(){"use strict";var t="/".replace(/([^/])$/,"$1/"),e=location.pathname,n=e.startsWith(t)&&decodeURI("/".concat(e.slice(t.length)));if(n){var a=document,c=a.head,r=a.createElement.bind(a),i=function(t,e,n){var a,c=e.r[t]||(null===(a=Object.entries(e.r).find((function(e){var n=e[0];return new RegExp("^".concat(n.replace(/\/:[^/]+/g,"/[^/]+").replace("/*","/.+"),"$")).test(t)})))||void 0===a?void 0:a[1]);return null==c?void 0:c.map((function(t){var a=e.f[t][1],c=e.f[t][0];return{type:c.split(".").pop(),url:"".concat(n.publicPath).concat(c),attrs:[["data-".concat(e.b),"".concat(e.p,":").concat(a)]]}}))}(n,{"p":"ant-design-pro","b":"webpack","f":[["p__AppManagement.94b1a1be.async.js",39],["51.e174e29e.async.js",51],["63.d3cf24e7.async.js",63],["134.6fc81d91.async.js",134],["169.57647c7d.async.js",169],["p__Welcome.61596037.async.js",185],["235.f7c67994.async.js",235],["247.3fe8fb10.async.js",247],["258.355f303f.async.js",258],["287.b21721be.async.js",287],["t__plugin-layout__Layout.6cae69f5.chunk.css",301],["t__plugin-layout__Layout.afe80231.async.js",301],["335.e3ecf74a.async.js",335],["p__User__Login__index.9d3ab92e.async.js",366],["p__DeviceOwnerApp.0a01face.async.js",371],["390.41467286.async.js",390],["393.b64a7324.async.js",393],["397.fb5f72c1.async.js",397],["p__SuperAdmin.e98ce7d0.async.js",455],["531.5028b00b.async.js",531],["p__Bind.b6ee068f.async.js",557],["559.016bfdbb.async.js",559],["p__404.0c100574.async.js",571],["635.84db70b9.async.js",635],["p__AdminManagement.292eeeb2.async.js",816],["p__AdvRecordList.58dfb141.async.js",857],["905.6e225d1b.async.js",905],["930.42e2019f.async.js",930]],"r":{"/*":[22,26],"/":[3,10,11,21,23,26],"/welcome":[3,4,5,16,21,27,10,11,23,26],"/super":[1,2,3,4,6,7,18,27,10,11,21,23,26],"/adminList":[1,4,6,9,24,3,10,11,21,23,26],"/bind":[3,4,16,17,20,21,27,10,11,23,26],"/appList":[0,1,2,3,4,6,9,12,17,19,26,27,10,11,21,23],"/advList/:code":[1,2,3,4,6,8,9,12,19,21,25,26,27,10,11,23],"/app/:code":[1,2,3,4,6,8,9,12,14,19,21,26,27,10,11,23],"/user/login":[2,3,4,6,9,13,19]}},{publicPath:"/"});null==i||i.forEach((function(t){var e,n=t.type,a=t.url;if("js"===n)(e=r("script")).src=a,e.async=!0;else{if("css"!==n)return;(e=r("link")).href=a,e.rel="preload",e.as="style"}t.attrs.forEach((function(t){e.setAttribute(t[0],t[1]||"")})),c.appendChild(e)}))}}();</script> <script>!function(){"use strict";var t="/".replace(/([^/])$/,"$1/"),e=location.pathname,n=e.startsWith(t)&&decodeURI("/".concat(e.slice(t.length)));if(n){var a=document,c=a.head,r=a.createElement.bind(a),i=function(t,e,n){var a,c=e.r[t]||(null===(a=Object.entries(e.r).find((function(e){var n=e[0];return new RegExp("^".concat(n.replace(/\/:[^/]+/g,"/[^/]+").replace("/*","/.+"),"$")).test(t)})))||void 0===a?void 0:a[1]);return null==c?void 0:c.map((function(t){var a=e.f[t][1],c=e.f[t][0];return{type:c.split(".").pop(),url:"".concat(n.publicPath).concat(c),attrs:[["data-".concat(e.b),"".concat(e.p,":").concat(a)]]}}))}(n,{"p":"ant-design-pro","b":"webpack","f":[["51.e174e29e.async.js",51],["63.d3cf24e7.async.js",63],["134.6fc81d91.async.js",134],["169.57647c7d.async.js",169],["p__Welcome.61596037.async.js",185],["235.f7c67994.async.js",235],["258.355f303f.async.js",258],["287.b21721be.async.js",287],["t__plugin-layout__Layout.6cae69f5.chunk.css",301],["t__plugin-layout__Layout.afe80231.async.js",301],["335.a2e08496.async.js",335],["p__User__Login__index.9d3ab92e.async.js",366],["p__DeviceOwnerApp.0a01face.async.js",371],["390.41467286.async.js",390],["393.b64a7324.async.js",393],["397.fb5f72c1.async.js",397],["417.ef345f0b.async.js",417],["p__SuperAdmin.e98ce7d0.async.js",455],["531.7b55d7f6.async.js",531],["p__Bind.b6ee068f.async.js",557],["559.016bfdbb.async.js",559],["p__404.0c100574.async.js",571],["635.477947e4.async.js",635],["p__App__AppManagement.c0407dd6.async.js",731],["p__AdminManagement.292eeeb2.async.js",816],["p__AdvRecordList.58dfb141.async.js",857],["886.6c2624b2.async.js",886],["905.6e225d1b.async.js",905],["930.fca7adbf.async.js",930]],"r":{"/*":[21,27],"/":[2,8,9,20,22,27],"/welcome":[2,3,4,14,20,28,8,9,22,27],"/super":[0,1,2,3,5,16,17,26,28,8,9,20,22,27],"/adminList":[0,3,5,7,24,2,8,9,20,22,27],"/bind":[2,3,14,15,19,20,28,8,9,22,27],"/appList":[0,1,2,3,5,7,10,15,16,18,23,27,28,8,9,20,22],"/advList/:code":[0,1,2,3,5,6,7,10,18,20,25,27,28,8,9,22],"/app/:code":[0,1,2,3,5,6,7,10,12,18,20,27,28,8,9,22],"/user/login":[1,2,3,5,7,11,18]}},{publicPath:"/"});null==i||i.forEach((function(t){var e,n=t.type,a=t.url;if("js"===n)(e=r("script")).src=a,e.async=!0;else{if("css"!==n)return;(e=r("link")).href=a,e.rel="preload",e.as="style"}t.attrs.forEach((function(t){e.setAttribute(t[0],t[1]||"")})),c.appendChild(e)}))}}();</script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="/umi.1e830525.js"></script> <script src="/umi.17046495.js"></script>
</body> </body>
</html> </html>

1
dist/p__AppManagement.94b1a1be.async.js

File diff suppressed because one or more lines are too long

1
dist/p__App__AppManagement.c0407dd6.async.js

File diff suppressed because one or more lines are too long

196
dist/umi.1e830525.js → dist/umi.17046495.js

File diff suppressed because one or more lines are too long

217
src/pages/App/AliPayConfigForm.tsx

@ -0,0 +1,217 @@
import { InboxOutlined, InfoCircleOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Tooltip } from 'antd';
import Upload, { UploadChangeParam } from 'antd/es/upload';
import { UploadFile } from 'antd/es/upload/interface';
import { RcFile } from 'antd/lib/upload';
import React, { useEffect } from 'react';
import { getAliPayConfig, saveAliPayConfig } from '../../services/matrix/matrixAppConfigController';
export type AliPayConfigFormProps = {
onCancel: () => void;
onSubmit: () => void;
updateModalOpen: boolean;
appCode: string;
};
interface UploadInfo extends UploadChangeParam<UploadFile<any>> {
file: UploadFile<any>;
}
const { TextArea } = Input;
const AliPayConfigForm: React.FC<AliPayConfigFormProps> = (props) => {
const [form] = Form.useForm();
const handleOk = () => {
form.submit();
};
const handleCancel = () => {
props.onCancel();
};
const fetchConfig = async (appCode: string) => {
const data = await getAliPayConfig({ appCode });
form.setFieldsValue(data.data);
};
useEffect(() => {
fetchConfig(props.appCode);
}, [props.appCode]);
const handleSaveApp = async (values: API.AliPayConfigBo) => {
try {
await saveAliPayConfig({ appCode: props.appCode }, values);
} catch (e) {
return;
}
props.onSubmit();
};
return (
<Modal title="编辑" visible={props.updateModalOpen} onOk={handleOk} onCancel={handleCancel}>
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 18 }} form={form} onFinish={handleSaveApp}>
<Form.Item
name="aliPayAppId"
label={
<>
AppId
<span style={{ marginLeft: 4 }}>
<Tooltip title="阿里云开放平台获取">
<InfoCircleOutlined />
</Tooltip>
</span>
</>
}
>
<Input />
</Form.Item>
<Form.Item
name="aliPayPrivateKey"
label={
<>
PrivateKey
<span style={{ marginLeft: 4 }}>
<Tooltip title="阿里云开放平台获取">
<InfoCircleOutlined />
</Tooltip>
</span>
</>
}
>
<Input />
</Form.Item>
<Form.Item
name="aliPayAppCert"
label={
<>
appCertPublicKey
<span style={{ marginLeft: 4 }}>
<Tooltip title="密钥工具生成">
<InfoCircleOutlined />
</Tooltip>
</span>
</>
}
>
<Input.Group compact>
<Form.Item
name="aliPayAppCert"
noStyle
rules={[{ required: true, message: '请输入或选择appCertPublicKey!' }]}
>
<TextArea autoSize={{ maxRows: 6 }} />
</Form.Item>
<Upload
name="file"
multiple={false}
onChange={(info: UploadInfo) => {
if (info.file.status !== 'uploading') {
const reader = new FileReader();
reader.onload = () => {
form.setFieldsValue({ aliPayAppCert: reader.result as string });
};
reader.readAsText(info.file.originFileObj as RcFile);
}
}}
showUploadList={false}
style={{ width: '100px' }}
>
<Button style={{ marginTop: 10 }} type="primary" icon={<InboxOutlined />}>
</Button>
</Upload>
</Input.Group>
</Form.Item>
<Form.Item
name="aliPayRootCert"
label={
<>
alipayRootCert
<span style={{ marginLeft: 4 }}>
<Tooltip title="密钥工具生成">
<InfoCircleOutlined />
</Tooltip>
</span>
</>
}
>
<Input.Group compact>
<Form.Item
name="aliPayRootCert"
noStyle
rules={[{ required: true, message: '请输入或选择alipayRootCert!' }]}
>
<TextArea autoSize={{ maxRows: 6 }} />
</Form.Item>
<Upload
name="file"
multiple={false}
onChange={(info: UploadInfo) => {
if (info.file.status !== 'uploading') {
const reader = new FileReader();
reader.onload = () => {
form.setFieldsValue({ aliPayRootCert: reader.result as string });
};
reader.readAsText(info.file.originFileObj as RcFile);
}
}}
showUploadList={false}
style={{ width: '100px' }}
>
<Button style={{ marginTop: 10 }} type="primary" icon={<InboxOutlined />}>
</Button>
</Upload>
</Input.Group>
</Form.Item>
<Form.Item
name="aliPayPublicCert"
label={
<>
alipayCertPublicKey
<span style={{ marginLeft: 4 }}>
<Tooltip title="密钥工具生成">
<InfoCircleOutlined />
</Tooltip>
</span>
</>
}
>
<Input.Group compact>
<Form.Item
name="aliPayPublicCert"
noStyle
rules={[{ required: true, message: '请输入或选择alipayCertPublicKey!' }]}
>
<TextArea autoSize={{ maxRows: 6 }} />
</Form.Item>
<Upload
name="file"
multiple={false}
onChange={(info: UploadInfo) => {
if (info.file.status !== 'uploading') {
const reader = new FileReader();
reader.onload = () => {
form.setFieldsValue({ aliPayPublicCert: reader.result as string });
};
reader.readAsText(info.file.originFileObj as RcFile);
}
}}
showUploadList={false}
style={{ width: '100px' }}
>
<Button style={{ marginTop: 10 }} type="primary" icon={<InboxOutlined />}>
</Button>
</Upload>
</Input.Group>
</Form.Item>
</Form>
</Modal>
);
};
export default AliPayConfigForm;

96
src/pages/App/AppBasicConfigForm.tsx

@ -0,0 +1,96 @@
import { Form, Input, Modal, Select } from 'antd';
import React, { useEffect, useState } from 'react';
import { getBasicConfig, saveBasicConfig } from '../../services/matrix/matrixAppConfigController';
export type AppBasicConfigFormProps = {
onCancel: () => void;
onSubmit: () => void;
updateModalOpen: boolean;
appCode: string | undefined;
};
const AppBasicConfigForm: React.FC<AppBasicConfigFormProps> = (props) => {
const [form] = Form.useForm();
const [editing, setEditing] = useState(false);
const handleOk = () => {
form.submit();
};
const handleCancel = () => {
props.onCancel();
};
const fetchConfig = async (appCode: string | undefined) => {
if (!appCode) {
form.resetFields();
return;
}
const data = await getBasicConfig({ appCode });
form.setFieldsValue(data.data);
};
useEffect(() => {
fetchConfig(props.appCode);
setEditing(props.appCode !== undefined);
if (!props.appCode) {
form.resetFields();
}
}, [props.appCode]);
const handleSaveApp = async (values: API.AppBasicConfig) => {
try {
await saveBasicConfig(values);
} catch (e) {
return;
}
props.onSubmit();
};
return (
<Modal title="编辑" visible={props.updateModalOpen} onOk={handleOk} onCancel={handleCancel}>
<Form form={form} onFinish={handleSaveApp}>
<Form.Item name="id" style={{ display: 'none' }}>
<Input type="hidden" />
</Form.Item>
<Form.Item name="code" style={{ display: 'none' }}>
<Input type="hidden" />
</Form.Item>
<Form.Item label="应用名" name="name">
<Input />
</Form.Item>
<Form.Item label="应用图片" name="img">
<Input />
</Form.Item>
<Form.Item label="下载地址" name="url">
<Input />
</Form.Item>
<Form.Item label="app版本" name="version">
<Input />
</Form.Item>
<Form.Item label="菜单栏中" name="hide">
<Select>
<Select.Option key="0" value={0}>
</Select.Option>
<Select.Option key="1" value={1}>
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="渠道" name="channel">
<Select disabled={editing}>
<Select.Option key="fanmiyou" value="fanmiyou">
</Select.Option>
<Select.Option key="qiji" value="qiji">
</Select.Option>
</Select>
</Form.Item>
</Form>
</Modal>
);
};
export default AppBasicConfigForm;

252
src/pages/App/AppManagement.tsx

@ -1,263 +1,17 @@
import { PageContainer, ProColumns, ProTable } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { useModel } from '@umijs/max'; import { useModel } from '@umijs/max';
import { Button, Form, Input, Modal, Popover, QRCode, Select, Tag } from 'antd';
import { useEffect, useState } from 'react';
import { appList, saveApp } from '@/services/matrix/admin';
import NormalAppManagement from './NormalAppManagement'; import NormalAppManagement from './NormalAppManagement';
import SuperAdminAppManagement from './SuperAdminAppManagement';
const AppManagement = () => { const AppManagement = () => {
const [visible, setVisible] = useState(false);
const [editing, setEditing] = useState(false);
const [form] = Form.useForm();
const [appArr, setAppArr] = useState<API.MatrixApp[]>([]);
const [filteredAppArr, setFilteredAppArr] = useState<API.MatrixApp[]>([]);
const { initialState } = useModel('@@initialState'); const { initialState } = useModel('@@initialState');
const currentUser = initialState?.currentUser; const currentUser = initialState?.currentUser;
const superAdmin = currentUser && currentUser.role && currentUser.role < 2; const superAdmin = currentUser && currentUser.role && currentUser.role < 2;
const handleEdit = (record: API.MatrixAppBo) => {
form.setFieldsValue(record);
setVisible(true);
setEditing(true);
};
const columns: ProColumns<API.MatrixAppBo>[] = [
{
title: '应用名',
dataIndex: 'name',
copyable: true,
hideInSearch: true,
},
{
title: '应用图片',
dataIndex: 'img',
hideInSearch: true,
renderText: (img: string) => <img style={{ width: '40px' }} src={img} />,
},
{
title: 'code',
dataIndex: 'code',
copyable: true,
hideInSearch: true,
},
{
title: '下载地址',
dataIndex: 'url',
width: 200,
hideInSearch: true,
renderText: (url: string) => (
<Popover
overlayInnerStyle={{ padding: 0 }}
content={<QRCode value={url || ''} bordered={false} />}
>
<a style={{ wordBreak: 'break-word' }}>{url}</a>
</Popover>
),
},
];
const superAdminColumns: ProColumns<API.MatrixAppBo>[] = [
{
title: '应用名',
dataIndex: 'name',
copyable: true,
hideInSearch: true,
},
{
title: '应用图片',
dataIndex: 'img',
hideInSearch: true,
renderText: (img: string) => <img style={{ width: '40px' }} src={img} />,
},
{
title: 'code',
dataIndex: 'code',
copyable: true,
hideInSearch: true,
},
{
title: '下载地址',
dataIndex: 'url',
width: 200,
hideInSearch: true,
renderText: (url: string) => (
<Popover
overlayInnerStyle={{ padding: 0 }}
content={<QRCode value={url || ''} bordered={false} />}
>
<a style={{ wordBreak: 'break-word' }}>{url}</a>
</Popover>
),
},
{
title: '操作',
width: 80,
hideInSearch: true,
renderText: (r: API.MatrixAppBo) => {
if (r.enableCash) {
return (
<a key="edit" onClick={() => handleEdit(r)}>
</a>
);
}
},
},
{
title: 'secret',
dataIndex: 'secret',
ellipsis: true,
copyable: true,
hideInSearch: true,
},
{
title: '菜单栏中',
dataIndex: 'hide',
hideInSearch: true,
renderText: (r: number) => {
if (r === 0) {
return <Tag color="blue"></Tag>;
} else {
return <Tag color="purple"></Tag>;
}
},
},
{
title: '渠道',
dataIndex: 'channel',
hideInSearch: true,
renderText: (r: string) => {
switch (r) {
case 'fanmiyou':
return '凡米游';
case 'qiji':
return '奇迹';
}
return r;
},
},
{
title: '操作',
width: 80,
hideInSearch: true,
renderText: (record: API.MatrixAppBo) => (
<a key="edit" onClick={() => handleEdit(record)}>
</a>
),
},
];
const handleOk = () => {
form.submit();
};
const handleCancel = () => {
setVisible(false);
};
const fetchApp = async () => {
const res = await appList();
if (res.data) {
setAppArr(res.data);
setFilteredAppArr(res.data);
}
};
useEffect(() => {
fetchApp();
}, []);
const handleNew = () => {
form.resetFields(); // 重置表单字段
setVisible(true);
setEditing(false);
};
const handleSaveApp = async (values: API.MatrixApp) => {
try {
await saveApp(values);
} catch (e) {
return;
}
setVisible(false);
fetchApp();
};
const handleSearch = (value: string) => {
const filtered = appArr.filter((item) => item?.name?.includes(value));
setFilteredAppArr(filtered);
};
if (!superAdmin) {
return <NormalAppManagement />;
}
return ( return (
<PageContainer> <PageContainer>
{currentUser && currentUser.role && currentUser.role < 2 && ( {superAdmin ? <SuperAdminAppManagement /> : <NormalAppManagement />}
<Button style={{ marginBottom: '20px' }} onClick={handleNew}>
</Button>
)}
<ProTable
search={{
defaultCollapsed: false, // 默认展开搜索框
optionRender: () => [
// 自定义搜索框
<Input
key="name"
placeholder="请输入应用名"
onChange={(e) => handleSearch(e.target.value)}
/>,
],
}}
onReset={() => {
setFilteredAppArr(appArr);
}}
columns={superAdmin ? superAdminColumns : columns}
dataSource={filteredAppArr}
/>
<Modal title="编辑" visible={visible} onOk={handleOk} onCancel={handleCancel}>
<Form form={form} onFinish={handleSaveApp}>
<Form.Item name="id" style={{ display: 'none' }}>
<Input type="hidden" />
</Form.Item>
<Form.Item name="code" style={{ display: 'none' }}>
<Input type="hidden" />
</Form.Item>
<Form.Item label="应用名" name="name">
<Input />
</Form.Item>
<Form.Item label="应用图片" name="img">
<Input />
</Form.Item>
<Form.Item label="下载地址" name="url">
<Input />
</Form.Item>
<Form.Item label="菜单栏中" name="hide">
<Select>
<Select.Option key="0" value={0}>
</Select.Option>
<Select.Option key="1" value={1}>
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="渠道" name="channel">
<Select disabled={editing}>
<Select.Option key="fanmiyou" value="fanmiyou">
</Select.Option>
<Select.Option key="qiji" value="qiji">
</Select.Option>
</Select>
</Form.Item>
</Form>
</Modal>
</PageContainer> </PageContainer>
); );
}; };

88
src/pages/App/CashConfigForm.tsx

@ -1,4 +1,4 @@
import { getAppNormalConfig, saveNormalConfig } from '@/services/matrix/matrixAppController'; import { getAppNormalConfig, saveNormalConfig } from '@/services/matrix/matrixAppConfigController';
import { InfoCircleOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { InfoCircleOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Col, Form, Input, InputNumber, Modal, Row, Tooltip } from 'antd'; import { Button, Col, Form, Input, InputNumber, Modal, Row, Tooltip } from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
@ -43,7 +43,7 @@ const CashConfigForm: React.FC<CashConfigFormProps> = (props) => {
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
xs: { span: 24 }, xs: { span: 24 },
sm: { span: 6 }, sm: { span: 8 },
}, },
wrapperCol: { wrapperCol: {
xs: { span: 24 }, xs: { span: 24 },
@ -52,8 +52,8 @@ const CashConfigForm: React.FC<CashConfigFormProps> = (props) => {
}; };
const formItemLayoutWithOutLabel = { const formItemLayoutWithOutLabel = {
wrapperCol: { wrapperCol: {
xs: { span: 24, offset: 0 }, xs: { span: 24, offset: 10 },
sm: { span: 20, offset: 6 }, sm: { span: 20, offset: 8 },
}, },
}; };
@ -63,44 +63,48 @@ const CashConfigForm: React.FC<CashConfigFormProps> = (props) => {
<Form.List name="moneyLadder"> <Form.List name="moneyLadder">
{(fields, { add, remove }) => ( {(fields, { add, remove }) => (
<> <>
{fields.map((field, index) => ( <Form.Item
<Form.Item // {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)} label={'提现梯度(分)'}
label={index === 0 ? '提现梯度' : ''} required={false}
required={false} >
key={field.key} <Row>
> {fields.map((field) => (
<Form.Item <Form.Item
{...field} // {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
validateTrigger={['onChange', 'onBlur']} label={''}
rules={[ required={false}
{ key={field.key}
required: true, >
whitespace: true, <Row align={'stretch'} style={{ marginRight: 10 }}>
message: '请输入金额或者删除本行', <Form.Item
}, {...field}
]} validateTrigger={['onChange', 'onBlur']}
noStyle rules={[
> {
<InputNumber placeholder="金额,单位分" style={{ width: '60%' }} /> required: true,
</Form.Item> message: '请输入金额或者删除',
{fields.length > 1 ? ( },
<MinusCircleOutlined ]}
className="dynamic-delete-button" noStyle
onClick={() => remove(field.name)} >
/> <InputNumber placeholder="金额,单位分" />
) : null} </Form.Item>
<MinusCircleOutlined
style={{ marginLeft: 4 }}
className="dynamic-delete-button"
onClick={() => remove(field.name)}
/>
</Row>
</Form.Item>
))}
</Row>
<Form.Item {...formItemLayoutWithOutLabel}>
<Button type="dashed" onClick={() => add()} icon={<PlusOutlined />}>
</Button>
</Form.Item> </Form.Item>
))}
<Form.Item {...formItemLayoutWithOutLabel}>
<Button
type="dashed"
onClick={() => add()}
style={{ width: '60%' }}
icon={<PlusOutlined />}
>
</Button>
</Form.Item> </Form.Item>
</> </>
)} )}
@ -187,7 +191,7 @@ const CashConfigForm: React.FC<CashConfigFormProps> = (props) => {
))} ))}
<Form.Item> <Form.Item>
<Button type="dashed" onClick={() => add()} block> <Button type="dashed" onClick={() => add()} block>
Add Field
</Button> </Button>
</Form.Item> </Form.Item>
</> </>

235
src/pages/App/SuperAdminAppManagement.tsx

@ -0,0 +1,235 @@
import { appList } from '@/services/matrix/admin';
import { PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
import { Button, Flex, Input, Popover, QRCode, Tag } from 'antd';
import { useEffect, useState } from 'react';
import AliPayConfigForm from './AliPayConfigForm';
import AppBasicConfigForm from './AppBasicConfigForm';
import UMengConfigForm from './UMengConfigForm';
import WxConfigForm from './WxConfigForm';
const SuperAdminAppManagement = () => {
const [visible, setVisible] = useState(false);
const [appArr, setAppArr] = useState<API.MatrixApp[]>([]);
const [filteredAppArr, setFilteredAppArr] = useState<API.MatrixApp[]>([]);
const [aliPayVisible, setAliPayVisible] = useState(false);
const [uMengVisible, setUMengVisible] = useState(false);
const [wxVisible, setWxVisible] = useState(false);
const [appCode, setAppCode] = useState<string>();
const superAdminColumns: ProColumns<API.MatrixAppBo>[] = [
{
title: '应用名',
dataIndex: 'name',
copyable: true,
hideInSearch: true,
},
{
title: '图片',
dataIndex: 'img',
hideInSearch: true,
renderText: (img: string) => <img style={{ width: '40px' }} src={img} />,
},
{
title: 'code',
dataIndex: 'code',
copyable: true,
hideInSearch: true,
},
{
title: '下载地址',
dataIndex: 'url',
width: 200,
hideInSearch: true,
renderText: (url: string) => (
<Popover
overlayInnerStyle={{ padding: 0 }}
content={<QRCode value={url || ''} bordered={false} />}
>
<a style={{ wordBreak: 'break-word' }}>{url}</a>
</Popover>
),
},
{
title: 'secret',
dataIndex: 'secret',
ellipsis: true,
copyable: true,
hideInSearch: true,
},
{
title: '菜单栏中',
dataIndex: 'hide',
hideInSearch: true,
renderText: (r: number) => {
if (r === 0) {
return <Tag color="blue"></Tag>;
} else {
return <Tag color="purple"></Tag>;
}
},
},
{
title: '渠道',
dataIndex: 'channel',
hideInSearch: true,
renderText: (r: string) => {
switch (r) {
case 'fanmiyou':
return '凡米游';
case 'qiji':
return '奇迹';
}
return r;
},
},
{
title: '操作',
width: 100,
hideInSearch: true,
renderText: (record: API.MatrixAppBo) => (
<Flex gap="small" wrap>
<Tag
color="magenta"
key="edit"
onClick={() => {
setAppCode(record.code);
setVisible(true);
}}
>
</Tag>
{record.enableCash && (
<Tag
key="ediyUMeng"
color="orange"
onClick={() => {
setAppCode(record.code);
setUMengVisible(true);
}}
>
</Tag>
)}
{record.enableCash && (
<Tag
key="ediyWx"
color="#87d068"
onClick={() => {
setAppCode(record.code);
setWxVisible(true);
}}
>
</Tag>
)}
<Tag
key="ediyAliPay"
color="#2db7f5"
onClick={() => {
setAppCode(record.code);
setAliPayVisible(true);
}}
>
</Tag>
</Flex>
),
},
];
const fetchApp = async () => {
const res = await appList();
if (res.data) {
setAppArr(res.data);
setFilteredAppArr(res.data);
}
};
useEffect(() => {
fetchApp();
}, []);
const handleSearch = (value: string) => {
const filtered = appArr.filter((item) => item?.name?.includes(value));
setFilteredAppArr(filtered);
};
return (
<PageContainer>
<Button
style={{ marginBottom: '20px' }}
onClick={() => {
setVisible(true);
setAppCode(undefined);
}}
>
</Button>
<ProTable
search={{
defaultCollapsed: false, // 默认展开搜索框
optionRender: () => [
// 自定义搜索框
<Input
key="name"
placeholder="请输入应用名"
onChange={(e) => handleSearch(e.target.value)}
/>,
],
}}
onReset={() => {
setFilteredAppArr(appArr);
}}
columns={superAdminColumns}
dataSource={filteredAppArr}
/>
{appCode && aliPayVisible && (
<AliPayConfigForm
updateModalOpen={aliPayVisible}
appCode={appCode || ''}
onCancel={() => setAliPayVisible(false)}
onSubmit={() => {
setAliPayVisible(false);
fetchApp();
}}
/>
)}
{appCode && uMengVisible && (
<UMengConfigForm
updateModalOpen={uMengVisible}
appCode={appCode || ''}
onCancel={() => setUMengVisible(false)}
onSubmit={() => {
setUMengVisible(false);
}}
/>
)}
{appCode && wxVisible && (
<WxConfigForm
updateModalOpen={wxVisible}
appCode={appCode || ''}
onCancel={() => setWxVisible(false)}
onSubmit={() => {
setWxVisible(false);
}}
/>
)}
<AppBasicConfigForm
updateModalOpen={visible}
appCode={appCode}
onCancel={() => setVisible(false)}
onSubmit={() => {
setVisible(false);
fetchApp();
}}
/>
</PageContainer>
);
};
export default SuperAdminAppManagement;

62
src/pages/App/UMengConfigForm.tsx

@ -0,0 +1,62 @@
import { Form, Input, Modal } from 'antd';
import React, { useEffect } from 'react';
import { getUMengConfig, saveUMengConfig } from '../../services/matrix/matrixAppConfigController';
export type UMengConfigFormProps = {
onCancel: () => void;
onSubmit: () => void;
updateModalOpen: boolean;
appCode: string;
};
const UMengConfigForm: React.FC<UMengConfigFormProps> = (props) => {
const [form] = Form.useForm();
const handleOk = () => {
form.submit();
};
const handleCancel = () => {
props.onCancel();
};
const fetchConfig = async (appCode: string) => {
const data = await getUMengConfig({ appCode });
form.setFieldsValue(data.data);
};
useEffect(() => {
fetchConfig(props.appCode);
}, [props.appCode]);
const handleSaveApp = async (values: API.UmengConfigBo) => {
try {
await saveUMengConfig({ appCode: props.appCode }, values);
} catch (e) {
return;
}
props.onSubmit();
};
return (
<Modal title="编辑" visible={props.updateModalOpen} onOk={handleOk} onCancel={handleCancel}>
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} form={form} onFinish={handleSaveApp}>
<Form.Item name="umengAppKeyAli" label="AppKey(阿里云)">
<Input />
</Form.Item>
<Form.Item name="umengAppSecret" label="AppSecret(阿里云)">
<Input />
</Form.Item>
<Form.Item name="umengAppCode" label="AppCode(阿里云)">
<Input />
</Form.Item>
<Form.Item name="umengAppKey" label="AppKey(友盟)">
<Input />
</Form.Item>
</Form>
</Modal>
);
};
export default UMengConfigForm;

57
src/pages/App/WxConfigForm.tsx

@ -0,0 +1,57 @@
import { saveWxConfig } from '@/services/matrix/matrixAppConfigController';
import { Form, Input, Modal } from 'antd';
import React, { useEffect } from 'react';
import { getWxConfig } from '../../services/matrix/matrixAppConfigController';
export type WxConfigFormProps = {
onCancel: () => void;
onSubmit: () => void;
updateModalOpen: boolean;
appCode: string;
};
const WxConfigForm: React.FC<WxConfigFormProps> = (props) => {
const [form] = Form.useForm();
const handleOk = () => {
form.submit();
};
const handleCancel = () => {
props.onCancel();
};
const fetchConfig = async (appCode: string) => {
const data = await getWxConfig({ appCode });
form.setFieldsValue(data.data);
};
useEffect(() => {
fetchConfig(props.appCode);
}, [props.appCode]);
const handleSaveApp = async (values: API.WxConfig) => {
try {
await saveWxConfig({ appCode: props.appCode }, values);
} catch (e) {
return;
}
props.onSubmit();
};
return (
<Modal title="编辑" visible={props.updateModalOpen} onOk={handleOk} onCancel={handleCancel}>
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 18 }} form={form} onFinish={handleSaveApp}>
<Form.Item name="appId" label="AppId">
<Input />
</Form.Item>
<Form.Item name="appSecret" label="AppSecret">
<Input />
</Form.Item>
</Form>
</Modal>
);
};
export default WxConfigForm;

15
src/services/matrix/admin.ts

@ -145,6 +145,21 @@ export async function adminLogin(
}); });
} }
/** 此处后端没有提供注释 GET /api/admin/mock */
export async function mock(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.mockParams,
options?: { [key: string]: any },
) {
return request<API.RVoid>('/api/admin/mock', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /api/admin/saveAdmin */ /** 此处后端没有提供注释 POST /api/admin/saveAdmin */
export async function saveAdmin(body: API.MatrixAdminBo, options?: { [key: string]: any }) { export async function saveAdmin(body: API.MatrixAdminBo, options?: { [key: string]: any }) {
return request<API.RVoid>('/api/admin/saveAdmin', { return request<API.RVoid>('/api/admin/saveAdmin', {

4
src/services/matrix/index.ts

@ -7,12 +7,12 @@ import * as appController from './appController';
import * as citrusAppController from './citrusAppController'; import * as citrusAppController from './citrusAppController';
import * as device from './device'; import * as device from './device';
import * as loginController from './loginController'; import * as loginController from './loginController';
import * as matrixAppController from './matrixAppController'; import * as matrixAppConfigController from './matrixAppConfigController';
import * as matrixController from './matrixController'; import * as matrixController from './matrixController';
import * as openController from './openController'; import * as openController from './openController';
import * as userController from './userController'; import * as userController from './userController';
export default { export default {
matrixAppController, matrixAppConfigController,
matrixController, matrixController,
loginController, loginController,
userController, userController,

170
src/services/matrix/matrixAppConfigController.ts

@ -0,0 +1,170 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';
/** 此处后端没有提供注释 GET /api/matrix/app/aliPayConfig */
export async function getAliPayConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getAliPayConfigParams,
options?: { [key: string]: any },
) {
return request<API.RAliPayConfigBo>('/api/matrix/app/aliPayConfig', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /api/matrix/app/aliPayConfig */
export async function saveAliPayConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.saveAliPayConfigParams,
body: API.AliPayConfigBo,
options?: { [key: string]: any },
) {
return request<API.RVoid>('/api/matrix/app/aliPayConfig', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: {
...params,
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /api/matrix/app/basicConfig */
export async function getBasicConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getBasicConfigParams,
options?: { [key: string]: any },
) {
return request<API.RAppBasicConfig>('/api/matrix/app/basicConfig', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /api/matrix/app/basicConfig */
export async function saveBasicConfig(body: API.AppBasicConfig, options?: { [key: string]: any }) {
return request<API.RVoid>('/api/matrix/app/basicConfig', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /api/matrix/app/normalConfig */
export async function getAppNormalConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getAppNormalConfigParams,
options?: { [key: string]: any },
) {
return request<API.RAppNormalConfig>('/api/matrix/app/normalConfig', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /api/matrix/app/normalConfig */
export async function saveNormalConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.saveNormalConfigParams,
body: API.AppNormalConfig,
options?: { [key: string]: any },
) {
return request<API.RVoid>('/api/matrix/app/normalConfig', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: {
...params,
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /api/matrix/app/uMengConfig */
export async function getUMengConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getUMengConfigParams,
options?: { [key: string]: any },
) {
return request<API.RUmengConfigBo>('/api/matrix/app/uMengConfig', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /api/matrix/app/uMengConfig */
export async function saveUMengConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.saveUMengConfigParams,
body: API.UmengConfigBo,
options?: { [key: string]: any },
) {
return request<API.RVoid>('/api/matrix/app/uMengConfig', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: {
...params,
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /api/matrix/app/wxConfig */
export async function getWxConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getWxConfigParams,
options?: { [key: string]: any },
) {
return request<API.RWxConfig>('/api/matrix/app/wxConfig', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /api/matrix/app/wxConfig */
export async function saveWxConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.saveWxConfigParams,
body: API.WxConfig,
options?: { [key: string]: any },
) {
return request<API.RVoid>('/api/matrix/app/wxConfig', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: {
...params,
},
data: body,
...(options || {}),
});
}

38
src/services/matrix/matrixAppController.ts

@ -1,38 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';
/** 此处后端没有提供注释 GET /api/matrix/app/normalConfig */
export async function getAppNormalConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getAppNormalConfigParams,
options?: { [key: string]: any },
) {
return request<API.RAppNormalConfig>('/api/matrix/app/normalConfig', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 此处后端没有提供注释 POST /api/matrix/app/normalConfig */
export async function saveNormalConfig(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.saveNormalConfigParams,
body: API.AppNormalConfig,
options?: { [key: string]: any },
) {
return request<API.RVoid>('/api/matrix/app/normalConfig', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: {
...params,
},
data: body,
...(options || {}),
});
}

98
src/services/matrix/typings.d.ts

@ -31,6 +31,26 @@ declare namespace API {
adminId?: number; adminId?: number;
}; };
type AliPayConfigBo = {
aliPayPublicCert?: string;
aliPayRootCert?: string;
aliPayAppCert?: string;
aliPayPrivateKey?: string;
aliPayAppId?: string;
};
type AppBasicConfig = {
id?: number;
code?: string;
secret?: string;
name?: string;
img?: string;
url?: string;
hide?: number;
channel?: string;
version?: number;
};
type AppCashConfig = { type AppCashConfig = {
moneyLadder?: number[]; moneyLadder?: number[];
noAuditMoney?: number; noAuditMoney?: number;
@ -106,6 +126,10 @@ declare namespace API {
deviceId: string; deviceId: string;
}; };
type getAliPayConfigParams = {
appCode: string;
};
type getAppDetailParams = { type getAppDetailParams = {
appCode: string; appCode: string;
}; };
@ -123,6 +147,18 @@ declare namespace API {
appCode?: string; appCode?: string;
}; };
type getBasicConfigParams = {
appCode: string;
};
type getUMengConfigParams = {
appCode: string;
};
type getWxConfigParams = {
appCode: string;
};
type grantAppParams = { type grantAppParams = {
appIds: string; appIds: string;
adminId: number; adminId: number;
@ -250,6 +286,7 @@ declare namespace API {
channel?: string; channel?: string;
hide?: number; hide?: number;
secret?: string; secret?: string;
version?: number;
umeng?: string; umeng?: string;
aliPay?: string; aliPay?: string;
wx?: string; wx?: string;
@ -263,6 +300,7 @@ declare namespace API {
}; };
type MatrixAppBo = { type MatrixAppBo = {
id?: number;
name?: string; name?: string;
code?: string; code?: string;
img?: string; img?: string;
@ -271,7 +309,6 @@ declare namespace API {
hide?: number; hide?: number;
secret?: string; secret?: string;
enableCash?: boolean; enableCash?: boolean;
config?: AppCashConfig;
}; };
type MatrixMockSchedule = { type MatrixMockSchedule = {
@ -293,6 +330,11 @@ declare namespace API {
createdAt?: string; createdAt?: string;
}; };
type mockParams = {
start: number;
end: number;
};
type offlineParams = { type offlineParams = {
deviceId: string; deviceId: string;
}; };
@ -315,6 +357,12 @@ declare namespace API {
sum?: number; sum?: number;
}; };
type RAliPayConfigBo = {
code?: number;
message?: string;
data?: AliPayConfigBo;
};
type RankList = { type RankList = {
list?: UserInfo[]; list?: UserInfo[];
myRank?: string; myRank?: string;
@ -327,6 +375,12 @@ declare namespace API {
size: number; size: number;
}; };
type RAppBasicConfig = {
code?: number;
message?: string;
data?: AppBasicConfig;
};
type RAppCashConfig = { type RAppCashConfig = {
code?: number; code?: number;
message?: string; message?: string;
@ -424,6 +478,12 @@ declare namespace API {
data?: LoginBo; data?: LoginBo;
}; };
type RLong = {
code?: number;
message?: string;
data?: number;
};
type RMatrixAdmin = { type RMatrixAdmin = {
code?: number; code?: number;
message?: string; message?: string;
@ -466,6 +526,12 @@ declare namespace API {
data?: STSInfo; data?: STSInfo;
}; };
type RUmengConfigBo = {
code?: number;
message?: string;
data?: UmengConfigBo;
};
type RUserBo = { type RUserBo = {
code?: number; code?: number;
message?: string; message?: string;
@ -478,10 +544,28 @@ declare namespace API {
data?: Record<string, any>; data?: Record<string, any>;
}; };
type RWxConfig = {
code?: number;
message?: string;
data?: WxConfig;
};
type saveAliPayConfigParams = {
appCode: string;
};
type saveNormalConfigParams = { type saveNormalConfigParams = {
appCode: string; appCode: string;
}; };
type saveUMengConfigParams = {
appCode: string;
};
type saveWxConfigParams = {
appCode: string;
};
type sendCodeParams = { type sendCodeParams = {
mobile: string; mobile: string;
scene: string; scene: string;
@ -497,6 +581,13 @@ declare namespace API {
endpoint?: string; endpoint?: string;
}; };
type UmengConfigBo = {
umengAppKeyAli?: string;
umengAppSecret?: string;
umengAppCode?: string;
umengAppKey?: string;
};
type updateScoreParams = { type updateScoreParams = {
score: number; score: number;
}; };
@ -531,6 +622,11 @@ declare namespace API {
appCode: string; appCode: string;
}; };
type WxConfig = {
appId?: string;
appSecret?: string;
};
type wxLoginParams = { type wxLoginParams = {
code: string; code: string;
appId: string; appId: string;

15
src/services/matrix/userController.ts

@ -44,3 +44,18 @@ export async function currentUser1(options?: { [key: string]: any }) {
...(options || {}), ...(options || {}),
}); });
} }
/** 此处后端没有提供注释 POST /api/citrus/user/saveAdvRecordV3 */
export async function saveAdvRecordV3(
body: API.MatrixAdvRecordEditBo,
options?: { [key: string]: any },
) {
return request<API.RLong>('/api/citrus/user/saveAdvRecordV3', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}

Loading…
Cancel
Save