feat(all): 初始化版本
This commit is contained in:
54
leopard-web/src/components/Markdown.tsx
Normal file
54
leopard-web/src/components/Markdown.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'chart.js/auto'
|
||||
import {MermaidDiagram} from '@lightenna/react-mermaid-diagram'
|
||||
import EChartsReact from 'echarts-for-react'
|
||||
import {trim} from 'licia'
|
||||
import {Chart} from 'react-chartjs-2'
|
||||
import Markdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
type MarkdownOptions = {
|
||||
content: string
|
||||
}
|
||||
|
||||
function MarkdownRender(options: MarkdownOptions) {
|
||||
return (
|
||||
<Markdown
|
||||
remarkPlugins={[
|
||||
remarkGfm
|
||||
]}
|
||||
children={options.content}
|
||||
components={{
|
||||
code: ({children, className, node, ...rest}) => {
|
||||
switch (trim(className || '')) {
|
||||
case 'language-mermaid':
|
||||
return (
|
||||
<MermaidDiagram
|
||||
children={children as string}
|
||||
/>
|
||||
)
|
||||
case 'language-chartjs':
|
||||
let chartjsData = eval(`(${children as string})`)
|
||||
return (
|
||||
<Chart
|
||||
{...chartjsData}
|
||||
/>
|
||||
)
|
||||
case 'language-echart':
|
||||
let echartData = eval(`(${children as string})`)
|
||||
return (
|
||||
<EChartsReact option={echartData}/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<code {...rest} className={className}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default MarkdownRender
|
||||
15
leopard-web/src/components/amis/MarkdownEnhance.scss
Normal file
15
leopard-web/src/components/amis/MarkdownEnhance.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
.markdown-enhance {
|
||||
tr {
|
||||
border-top: 1px solid #c6cbd1;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
table tr:nth-child(2n) {
|
||||
background: #f6f8fa;
|
||||
}
|
||||
}
|
||||
22
leopard-web/src/components/amis/MarkdownEnhance.tsx
Normal file
22
leopard-web/src/components/amis/MarkdownEnhance.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import {Renderer, type RendererProps} from "amis";
|
||||
import {once} from "licia";
|
||||
import React from "react";
|
||||
import Markdown from "../Markdown.tsx";
|
||||
import './MarkdownEnhance.scss'
|
||||
|
||||
const MarkdownEnhance: React.FC<RendererProps> = props => {
|
||||
return (
|
||||
<div className="markdown-enhance">
|
||||
<Markdown content={props.content}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const register = once(() => {
|
||||
Renderer({
|
||||
type: 'markdown-enhance',
|
||||
autoVar: true,
|
||||
})(React.memo(MarkdownEnhance))
|
||||
})
|
||||
|
||||
register()
|
||||
1
leopard-web/src/components/amis/Registry.ts
Normal file
1
leopard-web/src/components/amis/Registry.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './MarkdownEnhance.tsx'
|
||||
4
leopard-web/src/index.scss
Normal file
4
leopard-web/src/index.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
// 改写一些amis中控制不到的全局CSS
|
||||
button.btn-deleted:hover {
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
70
leopard-web/src/index.tsx
Normal file
70
leopard-web/src/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import {createRoot} from 'react-dom/client'
|
||||
import {createHashRouter, Navigate, type RouteObject, RouterProvider} from 'react-router'
|
||||
import './index.scss'
|
||||
import './components/amis/Registry.ts'
|
||||
import Overview from "./pages/Overview.tsx";
|
||||
import Root from "./pages/Root.tsx";
|
||||
import Test from "./pages/Test.tsx";
|
||||
import StockList from "./pages/stock/StockList.tsx";
|
||||
import StockDetail from './pages/stock/StockDetail.tsx';
|
||||
import TaskList from "./pages/task/TaskList.tsx";
|
||||
import TaskAdd from './pages/task/TaskAdd.tsx';
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
path: '/',
|
||||
Component: Root,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="/overview" replace/>,
|
||||
},
|
||||
{
|
||||
path: 'overview',
|
||||
Component: Overview,
|
||||
},
|
||||
{
|
||||
path: 'stock',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="/stock/list" replace/>,
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
Component: StockList,
|
||||
},
|
||||
{
|
||||
path: 'detail/:id',
|
||||
Component: StockDetail,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'task',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="/task/list" replace/>,
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
Component: TaskList,
|
||||
},
|
||||
{
|
||||
path: 'add',
|
||||
Component: TaskAdd,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'test',
|
||||
Component: Test,
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<RouterProvider router={createHashRouter(routes)}/>,
|
||||
)
|
||||
9
leopard-web/src/pages/Overview.tsx
Normal file
9
leopard-web/src/pages/Overview.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from "react";
|
||||
|
||||
function Overview() {
|
||||
return (
|
||||
<div className="overview"></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Overview)
|
||||
130
leopard-web/src/pages/Root.tsx
Normal file
130
leopard-web/src/pages/Root.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
DeploymentUnitOutlined,
|
||||
InfoCircleOutlined,
|
||||
MoneyCollectOutlined,
|
||||
UnorderedListOutlined
|
||||
} from "@ant-design/icons";
|
||||
import {type AppItemProps, ProLayout} from '@ant-design/pro-components'
|
||||
import {ConfigProvider} from 'antd'
|
||||
import {dateFormat} from 'licia'
|
||||
import React, {useMemo} from 'react'
|
||||
import {NavLink, Outlet, useLocation} from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const ProLayoutDiv = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.ant-menu-sub > .ant-menu-item {
|
||||
//padding-left: 28px !important;
|
||||
}
|
||||
`
|
||||
const apps: AppItemProps[] = []
|
||||
|
||||
const menus = {
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: '概览',
|
||||
icon: <InfoCircleOutlined/>,
|
||||
routes: [
|
||||
{
|
||||
path: '/overview',
|
||||
name: '概览',
|
||||
icon: <InfoCircleOutlined/>,
|
||||
},
|
||||
{
|
||||
path: '/stock',
|
||||
name: '股票',
|
||||
icon: <MoneyCollectOutlined/>,
|
||||
}, {
|
||||
path: '/task',
|
||||
name: '任务',
|
||||
icon: <UnorderedListOutlined/>,
|
||||
},
|
||||
{
|
||||
path: '/test',
|
||||
name: '测试',
|
||||
icon: <DeploymentUnitOutlined/>,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const Root: React.FC = () => {
|
||||
const location = useLocation()
|
||||
const currentYear = useMemo(() => dateFormat(new Date(), 'yyyy'), [])
|
||||
return (
|
||||
<ProLayoutDiv>
|
||||
<ProLayout
|
||||
collapsed={false}
|
||||
collapsedButtonRender={() => <></>}
|
||||
siderWidth={180}
|
||||
token={{
|
||||
colorTextAppListIcon: '#dfdfdf',
|
||||
colorTextAppListIconHover: '#ffffff',
|
||||
header: {
|
||||
colorBgHeader: '#292f33',
|
||||
colorHeaderTitle: '#ffffff',
|
||||
colorTextMenu: '#dfdfdf',
|
||||
colorTextMenuSecondary: '#dfdfdf',
|
||||
colorTextMenuSelected: '#ffffff',
|
||||
colorTextMenuActive: '#ffffff',
|
||||
colorBgMenuItemSelected: '#22272b',
|
||||
colorTextRightActionsItem: '#dfdfdf',
|
||||
},
|
||||
pageContainer: {
|
||||
paddingBlockPageContainerContent: 0,
|
||||
paddingInlinePageContainerContent: 0,
|
||||
marginBlockPageContainerContent: 0,
|
||||
marginInlinePageContainerContent: 0,
|
||||
},
|
||||
}}
|
||||
appList={apps}
|
||||
breakpoint={false}
|
||||
disableMobile={true}
|
||||
logo={<img src="icon.png" alt="logo"/>}
|
||||
title="金钱豹"
|
||||
route={menus}
|
||||
location={{pathname: location.pathname}}
|
||||
menu={{type: 'sub'}}
|
||||
menuItemRender={(item, defaultDom) =>
|
||||
<NavLink to={item.path || '/'}>{defaultDom}</NavLink>
|
||||
}
|
||||
fixSiderbar={true}
|
||||
layout="side"
|
||||
splitMenus={true}
|
||||
style={{minHeight: '100vh'}}
|
||||
contentStyle={{backgroundColor: 'white', padding: '10px 10px 10px 20px'}}
|
||||
menuFooterRender={props => {
|
||||
return (
|
||||
<div className="text-xs text-center" style={{userSelect: 'none', msUserSelect: 'none'}}>
|
||||
{props?.collapsed
|
||||
? undefined
|
||||
: <div>© 2023-{currentYear} 兰缘小妖</div>}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Card: {
|
||||
bodyPadding: 0,
|
||||
bodyPaddingSM: 0,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Outlet/>
|
||||
</ConfigProvider>
|
||||
</ProLayout>
|
||||
</ProLayoutDiv>
|
||||
)
|
||||
}
|
||||
|
||||
export default Root
|
||||
10
leopard-web/src/pages/Test.tsx
Normal file
10
leopard-web/src/pages/Test.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
|
||||
function Test() {
|
||||
return (
|
||||
<div className="test">
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Test)
|
||||
39
leopard-web/src/pages/stock/StockDetail.tsx
Normal file
39
leopard-web/src/pages/stock/StockDetail.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react'
|
||||
import {useParams} from 'react-router'
|
||||
import {amisRender, commonInfo, remoteMappings} from '../../util/amis.tsx'
|
||||
|
||||
function StockDetail() {
|
||||
const {id} = useParams()
|
||||
return (
|
||||
<div className="stock-detail">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '股票详情(${code} ${name})',
|
||||
initApi: `get:${commonInfo.baseUrl}/stock/detail/${id}`,
|
||||
body: [
|
||||
{
|
||||
type: 'property',
|
||||
items: [
|
||||
{label: '编码', content: '${code}'},
|
||||
{label: '名称', content: '${name}'},
|
||||
{label: '全名', content: '${fullname}'},
|
||||
{
|
||||
label: '市场',
|
||||
content: {
|
||||
...remoteMappings('stock_market', 'market'),
|
||||
value: '${market}',
|
||||
},
|
||||
},
|
||||
{label: '行业', content: '${industry}'},
|
||||
],
|
||||
},
|
||||
{type: 'divider'},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(StockDetail)
|
||||
159
leopard-web/src/pages/stock/StockList.tsx
Normal file
159
leopard-web/src/pages/stock/StockList.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
amisRender,
|
||||
commonInfo,
|
||||
crudCommonOptions,
|
||||
paginationTemplate,
|
||||
remoteMappings,
|
||||
remoteOptions,
|
||||
} from '../../util/amis.tsx'
|
||||
import {useNavigate} from 'react-router'
|
||||
|
||||
function StockList() {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<div className="stock-list">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '股票列表',
|
||||
body: [
|
||||
{
|
||||
type: 'crud',
|
||||
api: {
|
||||
method: 'post',
|
||||
url: `${commonInfo.baseUrl}/stock/list`,
|
||||
data: {
|
||||
query: {
|
||||
contain: {
|
||||
code: '${filter_code|default:undefined}',
|
||||
name: '${filter_keyword|default:undefined}',
|
||||
fullname: '${filter_keyword|default:undefined}',
|
||||
},
|
||||
inside: {
|
||||
market: '${filter_market|default:undefined}',
|
||||
industry: '${filter_industry|default:undefined}',
|
||||
},
|
||||
},
|
||||
page: {
|
||||
index: '${page}',
|
||||
size: '${perPage}',
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
column: 'code',
|
||||
direction: 'ASC',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...crudCommonOptions(),
|
||||
...paginationTemplate(15, undefined, ['filter-toggler']),
|
||||
filterTogglable: true,
|
||||
filterDefaultVisible: false,
|
||||
filter: {
|
||||
title: '快速搜索',
|
||||
mode: 'default',
|
||||
columnCount: 4,
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'filter_code',
|
||||
label: '编号',
|
||||
placeholder: '请输入编号',
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'filter_keyword',
|
||||
label: '关键字',
|
||||
placeholder: '请输入关键字',
|
||||
clearable: true,
|
||||
},
|
||||
{
|
||||
name: 'filter_market',
|
||||
label: '市场',
|
||||
...remoteOptions('select', 'stock_market'),
|
||||
multiple: true,
|
||||
extractValue: true,
|
||||
joinValues: false,
|
||||
clearable: true,
|
||||
checkAll: true,
|
||||
checkAllBySearch: true,
|
||||
defaultCheckAll: true,
|
||||
},
|
||||
{
|
||||
name: 'filter_industry',
|
||||
label: '行业',
|
||||
...remoteOptions('select', 'stock_industry'),
|
||||
searchable: true,
|
||||
multiple: true,
|
||||
extractValue: true,
|
||||
joinValues: false,
|
||||
clearable: true,
|
||||
checkAll: true,
|
||||
checkAllBySearch: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
name: 'code',
|
||||
label: '编号',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: '简称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'fullname',
|
||||
label: '全名',
|
||||
},
|
||||
{
|
||||
name: 'market',
|
||||
label: '市场',
|
||||
width: 100,
|
||||
...remoteMappings('stock_market', 'market'),
|
||||
},
|
||||
{
|
||||
name: 'industry',
|
||||
label: '行业',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 100,
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '详情',
|
||||
level: 'link',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate(`/stock/detail/${context.props.data['id']}`)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(StockList)
|
||||
27
leopard-web/src/pages/task/TaskAdd.tsx
Normal file
27
leopard-web/src/pages/task/TaskAdd.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import {amisRender, commonInfo} from '../../util/amis.tsx'
|
||||
|
||||
function TaskAdd() {
|
||||
return (
|
||||
<div className="task-add">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '任务添加',
|
||||
body: [
|
||||
{
|
||||
debug: commonInfo.debug,
|
||||
type: 'form',
|
||||
wrapWithPanel: false,
|
||||
mode: 'horizontal',
|
||||
labelAlign: 'left',
|
||||
body: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(TaskAdd)
|
||||
113
leopard-web/src/pages/task/TaskList.tsx
Normal file
113
leopard-web/src/pages/task/TaskList.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React from 'react'
|
||||
import {amisRender, commonInfo, crudCommonOptions, paginationTemplate, remoteMappings} from '../../util/amis.tsx'
|
||||
import {useNavigate} from 'react-router'
|
||||
|
||||
function TaskList() {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<div className="task-list">
|
||||
{amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
title: '任务列表',
|
||||
body: [
|
||||
{
|
||||
type: 'crud',
|
||||
api: {
|
||||
method: 'post',
|
||||
url: `${commonInfo.baseUrl}/task/list`,
|
||||
data: {
|
||||
page: {
|
||||
index: '${page}',
|
||||
size: '${perPage}',
|
||||
},
|
||||
},
|
||||
},
|
||||
...crudCommonOptions(),
|
||||
...paginationTemplate(
|
||||
15,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
type: 'action',
|
||||
label: '',
|
||||
icon: 'fa fa-plus',
|
||||
tooltip: '添加任务',
|
||||
tooltipPlacement: 'top',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate('/task/add')
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
label: '简称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: '描述',
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
...remoteMappings('task_status', 'status'),
|
||||
},
|
||||
{
|
||||
name: 'launchedTime',
|
||||
label: '启动时间',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: 'finishedTime',
|
||||
label: '结束时间',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 100,
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '详情',
|
||||
level: 'link',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'custom',
|
||||
// @ts-ignore
|
||||
script: (context, action, event) => {
|
||||
navigate(`/task/detail/${context.props.data['id']}`)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(TaskList)
|
||||
330
leopard-web/src/util/amis.tsx
Normal file
330
leopard-web/src/util/amis.tsx
Normal file
@@ -0,0 +1,330 @@
|
||||
import {AlertComponent, attachmentAdpator, makeTranslator, render, type Schema, ToastComponent} from 'amis'
|
||||
|
||||
import 'amis/lib/themes/antd.css'
|
||||
import 'amis/lib/helper.css'
|
||||
import 'amis/sdk/iconfont.css'
|
||||
import '@fortawesome/fontawesome-free/css/all.min.css'
|
||||
import axios from 'axios'
|
||||
import {isEqual} from 'licia'
|
||||
|
||||
export const commonInfo = {
|
||||
debug: isEqual(import.meta.env.MODE, 'development'),
|
||||
baseUrl: 'http://localhost:9786',
|
||||
}
|
||||
|
||||
const __ = makeTranslator('zh')
|
||||
|
||||
const responseAdaptor = () => (response: any) => {
|
||||
let payload = response.data || {} // blob 下可能会返回内容为空?
|
||||
if (payload.hasOwnProperty('errno')) {
|
||||
payload.status = payload.errno
|
||||
payload.msg = payload.errmsg
|
||||
} else if (payload.hasOwnProperty('no')) {
|
||||
payload.status = payload.no
|
||||
payload.msg = payload.error
|
||||
}
|
||||
return {
|
||||
...response,
|
||||
data: payload,
|
||||
}
|
||||
}
|
||||
|
||||
export const amisRender = (schema: Schema, data: Record<any, any> = {}) => {
|
||||
const theme = 'antd'
|
||||
const locale = 'zh-CN'
|
||||
return (
|
||||
<>
|
||||
<ToastComponent
|
||||
theme={theme}
|
||||
key="toast"
|
||||
position={'top-right'}
|
||||
locale={locale}
|
||||
/>
|
||||
<AlertComponent theme={theme} key="alert" locale={locale}/>
|
||||
{render(
|
||||
schema,
|
||||
{
|
||||
data: {
|
||||
...commonInfo,
|
||||
...data,
|
||||
},
|
||||
theme: theme,
|
||||
},
|
||||
{
|
||||
enableAMISDebug: commonInfo.debug,
|
||||
fetcher: async (api: any) => {
|
||||
let {url, method, data, responseType, config, headers} = api
|
||||
config = config || {}
|
||||
config.url = url
|
||||
config.withCredentials = true
|
||||
responseType && (config.responseType = responseType)
|
||||
|
||||
if (config.cancelExecutor) {
|
||||
config.cancelToken = new (axios as any).CancelToken(
|
||||
config.cancelExecutor,
|
||||
)
|
||||
}
|
||||
|
||||
config.headers = headers || {}
|
||||
config.method = method
|
||||
config.data = data
|
||||
|
||||
if (method === 'get' && data) {
|
||||
config.params = data
|
||||
} else if (data && data instanceof FormData) {
|
||||
// config.headers['Content-Type'] = 'multipart/form-data';
|
||||
} else if (
|
||||
data &&
|
||||
typeof data !== 'string' &&
|
||||
!(data instanceof Blob) &&
|
||||
!(data instanceof ArrayBuffer)
|
||||
) {
|
||||
data = JSON.stringify(data)
|
||||
config.headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
|
||||
// 支持返回各种报错信息
|
||||
config.validateStatus = function () {
|
||||
return true
|
||||
}
|
||||
|
||||
let response = await axios(config)
|
||||
response = await attachmentAdpator(response, __, api)
|
||||
response = responseAdaptor()(response)
|
||||
|
||||
if (response.status >= 400) {
|
||||
if (response.data) {
|
||||
// 主要用于 raw: 模式下,后端自己校验登录,
|
||||
if (
|
||||
response.status === 401 &&
|
||||
response.data.location &&
|
||||
response.data.location.startsWith('http')
|
||||
) {
|
||||
location.href = response.data.location.replace(
|
||||
'{{redirect}}',
|
||||
encodeURIComponent(location.href),
|
||||
)
|
||||
return new Promise(() => {
|
||||
})
|
||||
} else if (response.data.msg) {
|
||||
throw new Error(response.data.msg)
|
||||
} else {
|
||||
throw new Error(
|
||||
'System.requestError' + JSON.stringify(response.data, null, 2),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`${'System.requestErrorStatus'} ${response.status}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
},
|
||||
isCancel: (value: any) => (axios as any).isCancel(value),
|
||||
},
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function horizontalFormOptions() {
|
||||
return {
|
||||
mode: 'horizontal',
|
||||
horizontal: {
|
||||
leftFixed: 'sm',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function crudCommonOptions() {
|
||||
return {
|
||||
affixHeader: false,
|
||||
stopAutoRefreshWhenModalIsOpen: true,
|
||||
resizable: false,
|
||||
syncLocation: false,
|
||||
silentPolling: true,
|
||||
columnsTogglable: false,
|
||||
}
|
||||
}
|
||||
|
||||
export function readOnlyDialogOptions() {
|
||||
return {
|
||||
actions: [],
|
||||
showCloseButton: false,
|
||||
closeOnEsc: true,
|
||||
closeOnOutside: true,
|
||||
disabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
export function paginationCommonOptions(perPage = true, maxButtons = 5) {
|
||||
let option = {
|
||||
type: 'pagination',
|
||||
layout: [
|
||||
'pager',
|
||||
],
|
||||
maxButtons: maxButtons,
|
||||
showPageInput: false,
|
||||
perPageAvailable: [10, 15, 20, 50, 100, 200],
|
||||
}
|
||||
if (perPage) {
|
||||
option.layout.push('perPage')
|
||||
}
|
||||
return option
|
||||
}
|
||||
|
||||
export function paginationTemplate(perPage = 20, maxButtons = 5, extraHeaders: Array<Schema | string> = [], extraFooters: Array<Schema | string> = []) {
|
||||
return {
|
||||
perPage: perPage,
|
||||
headerToolbar: [
|
||||
'reload',
|
||||
paginationCommonOptions(true, maxButtons),
|
||||
...extraHeaders,
|
||||
],
|
||||
footerToolbar: [
|
||||
'statistics',
|
||||
paginationCommonOptions(true, maxButtons),
|
||||
...extraFooters,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function copyField(field: string, tips = '复制', ignoreLength = 0) {
|
||||
let tpl = ignoreLength === 0 ? `\${${field}}` : `\${TRUNCATE(${field}, ${ignoreLength})}`
|
||||
return {
|
||||
type: 'wrapper',
|
||||
size: 'none',
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
className: 'mr-1',
|
||||
tpl: tpl,
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
level: 'link',
|
||||
label: '',
|
||||
icon: 'fa fa-copy',
|
||||
size: 'xs',
|
||||
actionType: 'copy',
|
||||
content: `\$${field}`,
|
||||
tooltip: `${tips}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function mappingItem(label: string, value: string, color = 'bg-info') {
|
||||
return {
|
||||
label: label,
|
||||
value: value,
|
||||
color: color,
|
||||
}
|
||||
}
|
||||
|
||||
export function mappingField(field: string, mapping: Array<Record<string, string>>) {
|
||||
let mapData: Record<string, string> = {
|
||||
'*': `<span class='label bg-gray-300'>\${${field}}</span>`,
|
||||
}
|
||||
mapping.forEach(item => {
|
||||
mapData[item['value']] = `<span class='label ${item['color']}'>${item['label']}</span>`
|
||||
})
|
||||
return {
|
||||
type: 'mapping',
|
||||
value: `\${${field}}`,
|
||||
map: mapData,
|
||||
}
|
||||
}
|
||||
|
||||
export function filterableField(mapping: Array<Record<string, any>>, multiple = false) {
|
||||
return {
|
||||
multiple: multiple,
|
||||
options: [
|
||||
...mapping,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function time(field: string) {
|
||||
return {
|
||||
type: 'tpl',
|
||||
tpl: `\${IF(${field}, DATETOSTR(${field}, 'YYYY-MM-DD HH:mm:ss'), undefined)}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function pictureFromIds(field: string) {
|
||||
return `\${ARRAYMAP(${field},id => '${commonInfo.baseUrl}/upload/download/' + id)}`
|
||||
}
|
||||
|
||||
export const formInputFileStaticColumns = [
|
||||
{
|
||||
name: 'filename',
|
||||
label: '文件名',
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 140,
|
||||
buttons: [
|
||||
{
|
||||
type: 'action',
|
||||
label: '预览',
|
||||
level: 'link',
|
||||
icon: 'fas fa-eye',
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
label: '下载',
|
||||
level: 'link',
|
||||
icon: 'fa fa-download',
|
||||
actionType: 'ajax',
|
||||
// api: {
|
||||
// ...apiGet('${base}/upload/download/${id}'),
|
||||
// responseType: 'blob',
|
||||
// }
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export function formInputSingleFileStatic(field: string, label: string) {
|
||||
return {
|
||||
visibleOn: '${static}',
|
||||
type: 'control',
|
||||
label: label,
|
||||
required: true,
|
||||
body: {
|
||||
type: 'table',
|
||||
source: `\${${field}|asArray}`,
|
||||
columns: formInputFileStaticColumns,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function formInputMultiFileStatic(field: string, label: string) {
|
||||
return {
|
||||
visibleOn: '${static}',
|
||||
type: 'input-table',
|
||||
label: label,
|
||||
name: field,
|
||||
required: true,
|
||||
resizable: false,
|
||||
columns: formInputFileStaticColumns,
|
||||
}
|
||||
}
|
||||
|
||||
export function remoteOptions(type: string = 'select', name: string) {
|
||||
return {
|
||||
type: type,
|
||||
source: `get:${commonInfo.baseUrl}/constants/options/${name}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function remoteMappings(name: string, field: string) {
|
||||
return {
|
||||
type: 'mapping',
|
||||
source: `get:${commonInfo.baseUrl}/constants/mappings/${name}/${field}`,
|
||||
}
|
||||
}
|
||||
3
leopard-web/src/vite-env.d.ts
vendored
Normal file
3
leopard-web/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare const __APP_VERSION__: string
|
||||
Reference in New Issue
Block a user