Commit ba58e96a authored by wuhao's avatar wuhao 🎯

asder

parents
Pipeline #1185 canceled with stages
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
/lambda/
/scripts
/config
.history
public
dist
.umi
mock
\ No newline at end of file
module.exports = {
extends: [require.resolve('@umijs/fabric/dist/eslint')],
globals: {
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
page: true,
REACT_APP_ENV: true,
},
};
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
**/node_modules
# roadhog-api-doc ignore
/src/utils/request-temp.js
_roadhog-api-doc
# production
/dist
# misc
.DS_Store
npm-debug.log*
yarn-error.log
/coverage
.idea
package-lock.json
pnpm-lock.yaml
*bak
# visual studio code
.history
*.log
functions/*
.temp/**
# umi
.umi
.umi-production
# screenshot
screenshot
.firebase
.eslintcache
build
package
**/*.svg
package.json
.umi
.umi-production
/dist
.dockerignore
.DS_Store
.eslintignore
*.png
*.toml
docker
.editorconfig
Dockerfile*
.gitignore
.prettierignore
LICENSE
.eslintcache
*.lock
yarn-error.log
.history
CNAME
/build
/public
\ No newline at end of file
const fabric = require('@umijs/fabric');
module.exports = {
...fabric.prettier,
};
{
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "wangzy.sneak-mark"]
}
{
"editor.formatOnSave": true,
"prettier.requireConfig": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
# Ant Design Pro
This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use.
## Environment Prepare
Install `node_modules`:
```bash
npm install
```
or
```bash
yarn
```
## Provided Scripts
Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test.
Scripts provided in `package.json`. It's safe to modify or add additional script:
### Start project
```bash
npm start
```
### Build project
```bash
npm run build
```
### Check code style
```bash
npm run lint
```
You can also use script to auto fix some lint error:
```bash
npm run lint:fix
```
### Test code
```bash
npm test
```
## More
You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro).
export default [{"path":"/welcome","name":"欢迎","icon":"smile","component":"./Welcome"},{"name":"表格","icon":"table","path":"/system","routes":[{"name":"表格列表","path":"/system/staff","unkeepalive":true,"component":"./Welcome"},{"name":"测试","path":"/system/jiagou","component":"./Test"},{"component":"./404"}]},{"name":"左玲玲","path":"/zll","icon":"eye","component":"./zll"}];
// https://umijs.org/config/
import { defineConfig } from '@umijs/max';
import { join } from 'path';
import defaultSettings from './defaultSettings';
import proxy from './proxy';
import routes from './routes';
import authRoutes from './authRoutes';
const { REACT_APP_ENV } = process.env;
let temp = [];
function getList(tree) {
for (let item in tree) {
if (tree[item].routes) {
getList(tree[item].routes);
} else {
temp.push(tree[item]);
}
}
return temp;
}
const keepalive = getList(authRoutes)
?.filter((it) => {
return it?.path && !it?.unkeepalive;
})
.map((it) => it.path);
console.log(keepalive);
export default defineConfig({
outputPath: 'package',
hash: true,
antd: {
compact: true,
},
history: {
type: 'hash',
},
manifest: {
basePath: '/',
},
initialState: {},
model: {},
keepalive: keepalive,
layout: {
// https://umijs.org/zh-CN/plugins/plugin-layout
locale: false,
siderWidth: 208,
...defaultSettings,
},
// https://umijs.org/zh-CN/plugins/plugin-locale
locale: {
// default zh-CN
default: 'zh-CN',
antd: true,
// default true, when it is true, will use `navigator.language` overwrite default
baseNavigator: true,
},
// umi routes: https://umijs.org/docs/routing
routes,
access: {},
// Theme for antd: https://ant.design/docs/react/customize-theme-cn
theme: {
// 如果不想要 configProvide 动态设置主题需要把这个设置为 default
// 只有设置为 variable, 才能使用 configProvide 动态设置主色调
// https://ant.design/docs/react/customize-theme-variable-cn
'root-entry-name': 'variable',
},
ignoreMomentLocale: true,
proxy: proxy[REACT_APP_ENV || 'dev'],
// Fast Refresh 热更新
fastRefresh: true,
presets: ['umi-presets-pro'],
openAPI: [
{
requestLibPath: "import { request } from '@umijs/max'",
// 或者使用在线的版本
// schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
schemaPath: join(__dirname, 'oneapi.json'),
mock: false,
},
{
requestLibPath: "import { request } from '@umijs/max'",
schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
projectName: 'swagger',
},
],
});
const Settings = {
title: 'basepro',
name: 'basepro',
navTheme: 'light',
primaryColor: '#1890ff',
layout: 'side',
contentWidth: 'Fluid',
fixedHeader: false,
fixSiderbar: false,
pwa: false,
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
colorPrimary: '#1890ff',
siderMenuType: 'sub',
proxypath: '/emspro',
};
export default Settings;
This diff is collapsed.
/**
* 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
* -------------------------------
* The agent cannot take effect in the production environment
* so there is no configuration of the production environment
* For details, please see
* https://pro.ant.design/docs/deploy
*/
import defaultSetting from './defaultSettings';
let rewrite = `^${defaultSetting.proxypath}`;
export default {
dev: {
[defaultSetting.proxypath]: {
// 要代理的地址
target: 'http://192.168.40.111:8000',
changeOrigin: true,
pathRewrite: {
[rewrite]: '',
},
},
'/staticfile/': {
target: 'http://192.168.40.2/',
changeOrigin: true,
pathRewrite: {
'^/staticfile': '',
},
},
},
test: {
'/api/': {
target: 'https://proapi.azurewebsites.net',
changeOrigin: true,
pathRewrite: { '^': '' },
},
},
pre: {
'/api/': {
target: 'your pre url',
changeOrigin: true,
pathRewrite: { '^': '' },
},
},
};
import authRoutes from './authRoutes';
let allauth = authRoutes; // .map((it) => ({ ...it, wrappers: ['@/wrappers/auth'] }));
export default [
{
path: '/user',
layout: false,
routes: [
{
name: '登录',
path: '/user/login',
component: './User/Login',
},
{
component: './404',
},
],
},
...allauth,
{
path: '/',
redirect: '/welcome',
},
{
path: '/404',
component: './404',
},
{
path: '/403',
component: './403',
},
{
component: './404',
},
];
module.exports = {
testURL: 'http://localhost:8000',
verbose: false,
extraSetupFiles: ['./tests/setupTests.js'],
globals: {
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false,
localStorage: null,
},
};
{
"compilerOptions": {
"jsx": "react-jsx",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
import { Request, Response } from 'express';
import moment from 'moment';
import { parse } from 'url';
// mock tableListDataSource
const genList = (current: number, pageSize: number) => {
const tableListDataSource: API.RuleListItem[] = [];
for (let i = 0; i < pageSize; i += 1) {
const index = (current - 1) * 10 + i;
tableListDataSource.push({
key: index,
disabled: i % 6 === 0,
href: 'https://ant.design',
avatar: [
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
][i % 2],
name: `TradeCode ${index}`,
owner: '曲丽丽',
desc: '这是一段描述',
callNo: Math.floor(Math.random() * 1000),
status: Math.floor(Math.random() * 10) % 4,
updatedAt: moment().format('YYYY-MM-DD'),
createdAt: moment().format('YYYY-MM-DD'),
progress: Math.ceil(Math.random() * 100),
});
}
tableListDataSource.reverse();
return tableListDataSource;
};
let tableListDataSource = genList(1, 100);
function getRule(req: Request, res: Response, u: string) {
let realUrl = u;
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
realUrl = req.url;
}
const { current = 1, pageSize = 10 } = req.query;
const params = parse(realUrl, true).query as unknown as API.PageParams &
API.RuleListItem & {
sorter: any;
filter: any;
};
let dataSource = [...tableListDataSource].slice(
((current as number) - 1) * (pageSize as number),
(current as number) * (pageSize as number),
);
if (params.sorter) {
const sorter = JSON.parse(params.sorter);
dataSource = dataSource.sort((prev, next) => {
let sortNumber = 0;
Object.keys(sorter).forEach((key) => {
if (sorter[key] === 'descend') {
if (prev[key] - next[key] > 0) {
sortNumber += -1;
} else {
sortNumber += 1;
}
return;
}
if (prev[key] - next[key] > 0) {
sortNumber += 1;
} else {
sortNumber += -1;
}
});
return sortNumber;
});
}
if (params.filter) {
const filter = JSON.parse(params.filter as any) as {
[key: string]: string[];
};
if (Object.keys(filter).length > 0) {
dataSource = dataSource.filter((item) => {
return Object.keys(filter).some((key) => {
if (!filter[key]) {
return true;
}
if (filter[key].includes(`${item[key]}`)) {
return true;
}
return false;
});
});
}
}
if (params.name) {
dataSource = dataSource.filter((data) => data?.name?.includes(params.name || ''));
}
const result = {
data: dataSource,
total: tableListDataSource.length,
success: true,
pageSize,
current: parseInt(`${params.current}`, 10) || 1,
};
return res.json(result);
}
function postRule(req: Request, res: Response, u: string, b: Request) {
let realUrl = u;
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
realUrl = req.url;
}
const body = (b && b.body) || req.body;
const { method, name, desc, key } = body;
switch (method) {
/* eslint no-case-declarations:0 */
case 'delete':
tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1);
break;
case 'post':
(() => {
const i = Math.ceil(Math.random() * 10000);
const newRule: API.RuleListItem = {
key: tableListDataSource.length,
href: 'https://ant.design',
avatar: [
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
][i % 2],
name,
owner: '曲丽丽',
desc,
callNo: Math.floor(Math.random() * 1000),
status: Math.floor(Math.random() * 10) % 2,
updatedAt: moment().format('YYYY-MM-DD'),
createdAt: moment().format('YYYY-MM-DD'),
progress: Math.ceil(Math.random() * 100),
};
tableListDataSource.unshift(newRule);
return res.json(newRule);
})();
return;
case 'update':
(() => {
let newRule = {};
tableListDataSource = tableListDataSource.map((item) => {
if (item.key === key) {
newRule = { ...item, desc, name };
return { ...item, desc, name };
}
return item;
});
return res.json(newRule);
})();
return;
default:
break;
}
const result = {
list: tableListDataSource,
pagination: {
total: tableListDataSource.length,
},
};
res.json(result);
}
export default {
'GET /api/rule': getRule,
'POST /api/rule': postRule,
};
import { Request, Response } from 'express';
const getNotices = (req: Request, res: Response) => {
res.json({
data: [
{
id: '000000001',
avatar:
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/MSbDR4FR2MUAAAAAAAAAAAAAFl94AQBr',
title: '你收到了 14 份新周报',
datetime: '2017-08-09',
type: 'notification',
},
{
id: '000000002',
avatar:
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/hX-PTavYIq4AAAAAAAAAAAAAFl94AQBr',
title: '你推荐的 曲妮妮 已通过第三轮面试',
datetime: '2017-08-08',
type: 'notification',
},
{
id: '000000003',
avatar:
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/jHX5R5l3QjQAAAAAAAAAAAAAFl94AQBr',
title: '这种模板可以区分多种通知类型',
datetime: '2017-08-07',
read: true,
type: 'notification',
},
{
id: '000000004',
avatar:
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Wr4mQqx6jfwAAAAAAAAAAAAAFl94AQBr',
title: '左侧图标用于区分不同的类型',
datetime: '2017-08-07',
type: 'notification',
},
{
id: '000000005',
avatar:
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Mzj_TbcWUj4AAAAAAAAAAAAAFl94AQBr',
title: '内容不要超过两行字,超出时自动截断',
datetime: '2017-08-07',
type: 'notification',
},
{
id: '000000006',
avatar:
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/eXLzRbPqQE4AAAAAAAAAAAAAFl94AQBr',
title: '曲丽丽 评论了你',
description: '描述信息描述信息描述信息',
datetime: '2017-08-07',
type: 'message',
clickClose: true,
},
{
id: '000000007',
avatar:
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/w5mRQY2AmEEAAAAAAAAAAAAAFl94AQBr',
title: '朱偏右 回复了你',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
type: 'message',
clickClose: true,
},
{
id: '000000008',
avatar:
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/wPadR5M9918AAAAAAAAAAAAAFl94AQBr',
title: '标题',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
type: 'message',
clickClose: true,
},
{
id: '000000009',
title: '任务名称',
description: '任务需要在 2017-01-12 20:00 前启动',
extra: '未开始',
status: 'todo',
type: 'event',
},
{
id: '000000010',
title: '第三方紧急代码变更',
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
extra: '马上到期',
status: 'urgent',
type: 'event',
},
{
id: '000000011',
title: '信息安全考试',
description: '指派竹尔于 2017-01-09 前完成更新并发布',
extra: '已耗时 8 天',
status: 'doing',
type: 'event',
},
{
id: '000000012',
title: 'ABCD 版本发布',
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
extra: '进行中',
status: 'processing',
type: 'event',
},
],
});
};
export default {
'GET /api/notices': getNotices,
};
export default {
'/api/auth_routes': {
'/form/advanced-form': { authority: ['admin', 'user'] },
},
};
import { Request, Response } from 'express';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
async function getFakeCaptcha(req: Request, res: Response) {
await waitTime(2000);
return res.json('captcha-xxx');
}
const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env;
/**
* 当前用户的权限,如果为空代表没登录
* current user access, if is '', user need login
* 如果是 pro 的预览,默认是有权限的
*/
let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : '';
const getAccess = () => {
return access;
};
// 代码中会兼容本地 service mock 以及部署站点的静态数据
export default {
// 支持值为 Object 和 Array
'GET /api/currentUser': (req: Request, res: Response) => {
if (!getAccess()) {
res.status(401).send({
data: {
isLogin: false,
},
errorCode: '401',
errorMessage: '请先登录!',
success: true,
});
return;
}
res.send({
success: true,
data: {
name: 'Serati Ma',
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
userid: '00000001',
email: 'antdesign@alipay.com',
signature: '海纳百川,有容乃大',
title: '交互专家',
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
tags: [
{
key: '0',
label: '很有想法的',
},
{
key: '1',
label: '专注设计',
},
{
key: '2',
label: '辣~',
},
{
key: '3',
label: '大长腿',
},
{
key: '4',
label: '川妹子',
},
{
key: '5',
label: '海纳百川',
},
],
notifyCount: 12,
unreadCount: 11,
country: 'China',
access: getAccess(),
geographic: {
province: {
label: '浙江省',
key: '330000',
},
city: {
label: '杭州市',
key: '330100',
},
},
address: '西湖区工专路 77 号',
phone: '0752-268888888',
},
});
},
// GET POST 可省略
'GET /api/users': [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
],
'POST /api/login/account': async (req: Request, res: Response) => {
const { password, username, type } = req.body;
await waitTime(2000);
if (password === 'ant.design' && username === 'admin') {
res.send({
status: 'ok',
type,
currentAuthority: 'admin',
});
access = 'admin';
return;
}
if (password === 'ant.design' && username === 'user') {
res.send({
status: 'ok',
type,
currentAuthority: 'user',
});
access = 'user';
return;
}
if (type === 'mobile') {
res.send({
status: 'ok',
type,
currentAuthority: 'admin',
});
access = 'admin';
return;
}
res.send({
status: 'error',
type,
currentAuthority: 'guest',
});
access = 'guest';
},
'POST /api/login/outLogin': (req: Request, res: Response) => {
access = '';
res.send({ data: {}, success: true });
},
'POST /api/register': (req: Request, res: Response) => {
res.send({ status: 'ok', currentAuthority: 'user', success: true });
},
'GET /api/500': (req: Request, res: Response) => {
res.status(500).send({
timestamp: 1513932555104,
status: 500,
error: 'error',
message: 'error',
path: '/base/category/list',
});
},
'GET /api/404': (req: Request, res: Response) => {
res.status(404).send({
timestamp: 1513932643431,
status: 404,
error: 'Not Found',
message: 'No message available',
path: '/base/category/list/2121212',
});
},
'GET /api/403': (req: Request, res: Response) => {
res.status(403).send({
timestamp: 1513932555104,
status: 403,
error: 'Forbidden',
message: 'Forbidden',
path: '/base/category/list',
});
},
'GET /api/401': (req: Request, res: Response) => {
res.status(401).send({
timestamp: 1513932555104,
status: 401,
error: 'Unauthorized',
message: 'Unauthorized',
path: '/base/category/list',
});
},
'GET /api/login/captcha': getFakeCaptcha,
};
{
"name": "ant-design-pro",
"version": "6.0.0-beta.1",
"private": true,
"description": "An out-of-box UI solution for enterprise applications",
"scripts": {
"analyze": "cross-env ANALYZE=1 max build",
"build": "max build",
"deploy": "npm run build && npm run gh-pages",
"dev": "npm run start:dev",
"gh-pages": "gh-pages -d dist",
"i18n-remove": "pro i18n-remove --locale=zh-CN --write",
"postinstall": "max setup",
"lint": "npm run lint:js && npm run lint:prettier && npm run tsc",
"lint-staged": "lint-staged",
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src ",
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
"lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto",
"openapi": "max openapi",
"playwright": "playwright install && playwright test",
"prepare": "husky install",
"prettier": "prettier -c --write \"src/**/*\"",
"serve": "umi-serve",
"start": "cross-env UMI_ENV=dev max dev",
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev",
"start:no-mock": "cross-env MOCK=none UMI_ENV=dev max dev",
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
"test": "max test",
"test:component": "max test ./src/components",
"test:e2e": "node ./tests/run-tests.js",
"tsc": "tsc --noEmit"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
"**/*.{js,jsx,tsx,ts,less,md,json}": [
"prettier --write"
]
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 10"
],
"dependencies": {
"@ant-design/icons": "^4.7.0",
"@ant-design/pro-components": "^2.0.0",
"@umijs/route-utils": "^2.0.0",
"ahooks": "^3.7.0",
"antd": "^4.23.1",
"antd-img-crop": "^4.2.4",
"braft-editor": "^2.3.9",
"classnames": "^2.3.0",
"crypto-js": "^4.1.1",
"lodash": "^4.17.0",
"moment": "^2.29.4",
"omit.js": "^2.0.2",
"query-string": "^7.1.1",
"rc-menu": "^9.1.0",
"rc-util": "^5.16.0",
"react": "^17.0.0",
"react-custom-scrollbars": "^4.2.1",
"react-dev-inspector": "^1.7.0",
"react-dom": "^17.0.0",
"react-helmet-async": "^1.2.0",
"react-resizable": "^3.0.4",
"umi-request": "^1.4.0"
},
"devDependencies": {
"@ant-design/pro-cli": "^2.1.0",
"@playwright/test": "^1.17.0",
"@types/classnames": "^2.3.1",
"@types/express": "^4.17.0",
"@types/history": "^4.7.0",
"@types/jest": "^26.0.0",
"@types/lodash": "^4.14.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/react-helmet": "^6.1.0",
"@umijs/fabric": "^2.11.1",
"@umijs/max": "^4.0.0-rc.22",
"@umijs/openapi": "^1.3.0",
"cross-env": "^7.0.0",
"cross-port-killer": "^1.3.0",
"detect-installer": "^1.0.0",
"eslint": "^7.32.0",
"eslint-json": "^2.0.0",
"gh-pages": "^3.2.0",
"husky": "^7.0.4",
"jsdom-global": "^3.0.0",
"lint-staged": "^10.0.0",
"mockjs": "^1.1.0",
"prettier": "^2.5.0",
"swagger-ui-dist": "^4.12.0",
"typescript": "^4.5.0",
"umi-presets-pro": "^1.0.1",
"umi-serve": "^1.9.10"
},
"engines": {
"node": ">=12.0.0"
}
}
// playwright.config.ts
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
use: {
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
],
};
export default config;
preview.pro.ant.design
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" version="1.1" viewBox="0 0 200 200"><title>Group 28 Copy 5</title><desc>Created with Sketch.</desc><defs><linearGradient id="linearGradient-1" x1="62.102%" x2="108.197%" y1="0%" y2="37.864%"><stop offset="0%" stop-color="#4285EB"/><stop offset="100%" stop-color="#2EC7FF"/></linearGradient><linearGradient id="linearGradient-2" x1="69.644%" x2="54.043%" y1="0%" y2="108.457%"><stop offset="0%" stop-color="#29CDFF"/><stop offset="37.86%" stop-color="#148EFF"/><stop offset="100%" stop-color="#0A60FF"/></linearGradient><linearGradient id="linearGradient-3" x1="69.691%" x2="16.723%" y1="-12.974%" y2="117.391%"><stop offset="0%" stop-color="#FA816E"/><stop offset="41.473%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient><linearGradient id="linearGradient-4" x1="68.128%" x2="30.44%" y1="-35.691%" y2="114.943%"><stop offset="0%" stop-color="#FA8E7D"/><stop offset="51.264%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient></defs><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="logo" transform="translate(-20.000000, -20.000000)"><g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)"><g id="Group-27-Copy-3"><g id="Group-25" fill-rule="nonzero"><g id="2"><path id="Shape" fill="url(#linearGradient-1)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/><path id="Shape" fill="url(#linearGradient-2)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/></g><path id="Shape" fill="url(#linearGradient-3)" d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z"/></g><ellipse id="Combined-Shape" cx="100.519" cy="100.437" fill="url(#linearGradient-4)" rx="23.6" ry="23.581"/></g></g></g></g></svg>
\ No newline at end of file
<svg width="42" height="42" xmlns="http://www.w3.org/2000/svg">
<g>
<path fill="#070707" d="m6.717392,13.773912l5.6,0c2.8,0 4.7,1.9 4.7,4.7c0,2.8 -2,4.7 -4.9,4.7l-2.5,0l0,4.3l-2.9,0l0,-13.7zm2.9,2.2l0,4.9l1.9,0c1.6,0 2.6,-0.9 2.6,-2.4c0,-1.6 -0.9,-2.4 -2.6,-2.4l-1.9,0l0,-0.1zm8.9,11.5l2.7,0l0,-5.7c0,-1.4 0.8,-2.3 2.2,-2.3c0.4,0 0.8,0.1 1,0.2l0,-2.4c-0.2,-0.1 -0.5,-0.1 -0.8,-0.1c-1.2,0 -2.1,0.7 -2.4,2l-0.1,0l0,-1.9l-2.7,0l0,10.2l0.1,0zm11.7,0.1c-3.1,0 -5,-2 -5,-5.3c0,-3.3 2,-5.3 5,-5.3s5,2 5,5.3c0,3.4 -1.9,5.3 -5,5.3zm0,-2.1c1.4,0 2.2,-1.1 2.2,-3.2c0,-2 -0.8,-3.2 -2.2,-3.2c-1.4,0 -2.2,1.2 -2.2,3.2c0,2.1 0.8,3.2 2.2,3.2z" class="st0" id="Ant-Design-Pro"/>
</g>
</svg>
\ No newline at end of file
/**
* @see https://umijs.org/zh-CN/plugins/plugin-access
* */
export default function access(initialState) {
const { currentUser } = initialState ?? {};
return {
canAdmin: currentUser && currentUser.access === 'admin',
};
}
import Footer from '@/components/Footer';
import RightContent from '@/components/RightContent';
import { SettingDrawer } from '@ant-design/pro-components';
import { history, Link, matchRoutes } from '@umijs/max';
import defaultSettings from '../config/defaultSettings';
import { queryCurrentUser, getMenu } from './services/login';
import TagView from '@/components/TagView';
import SiderMenu from '@/components/SiderMenu';
import * as Ant4Icons from '@ant-design/icons';
import { createElement } from 'react';
const { LinkOutlined } = Ant4Icons;
const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login';
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
function strToHump(str) {
let strArr = str.split('-');
for (let i = 0; i < strArr.length; i++) {
strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substr(1);
}
let res = strArr.join('');
return res + 'Outlined';
}
export async function getInitialState() {
let token = localStorage.getItem('TOKENES');
const fetchUserInfo = async () => {
try {
const msg = await queryCurrentUser();
return msg.data;
} catch (error) {
history.push(loginPath);
}
return undefined;
}; // 如果是登录页面,不执行
const getmenuData = async () => {
try {
const res = await getMenu();
return res.data;
} catch (error) {
message.error(res.msg);
}
return undefined;
}; // 如果是登录页面,不执行
if (history.location.pathname !== loginPath && token) {
const currentUserData = await fetchUserInfo();
let menuData;
if (currentUserData?.data?.userName) {
menuData = await getmenuData();
}
return {
fetchUserInfo,
currentUser: currentUserData?.data,
settings: defaultSettings,
newMenu: menuData,
getmenuData,
collapsed: false,
};
}
return {
fetchUserInfo,
settings: defaultSettings,
getmenuData,
collapsed: false,
tagList: [],
};
}
// ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout = ({ initialState, setInitialState }) => {
let token = localStorage.getItem('TOKENES');
return {
disableContentMargin: false,
waterMarkProps: {
content: initialState?.currentUser?.name,
},
collapsed: initialState.collapsed,
onCollapse: (cols) => {
setInitialState((s) => ({ ...s, collapsed: cols }));
},
footerRender: () => <Footer />,
onPageChange: () => {
const { location } = history;
// 如果没有登录,重定向到 login
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
}
},
links: isDev
? [
<Link key="openapi" to="/umi/plugin/openapi" target="_blank">
<LinkOutlined />
<span>OpenAPI 文档</span>
</Link>,
]
: [],
//接口获取菜单数据
menu: {
// 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
params: {
userId: initialState?.currentUser?.id,
},
request: (params, defaultMenuData) => {
let lastArr = initialState?.newMenu?.userHavePermList
? JSON.parse(JSON.stringify(initialState?.newMenu?.userHavePermList))
: [],
newArr = [
{
path: '/welcome',
name: '首页',
icon: 'smile',
component: './Welcome',
haveChildren: false,
key: '000000',
parentKey: '0',
routes: [],
children: null,
title: null,
},
].concat(lastArr);
return newArr.map((item, index) => {
if (item.icon) {
const icon = strToHump(item.icon),
ItemIcon = Ant4Icons[icon];
item.icon = createElement(ItemIcon);
}
return item;
});
},
locale: false,
},
menuRender: (props, defaultDom) => {
if (props.isMobile) {
return defaultDom;
} else {
return <SiderMenu {...props} />;
}
},
// 自定义 403 页面
unAccessible: <div>unAccessible</div>,
noFound: <div>noFound</div>,
// 增加一个 loading 的状态
childrenRender: (children, props) => {
// if (initialState?.loading) return <PageLoading />;
return (
<>
{initialState?.currentUser && location.pathname !== loginPath ? (
<TagView home="/welcome">{children}</TagView>
) : (
children
)}
</>
);
},
...initialState?.settings,
layout: false,
headerContentRender: () => <RightContent />,
rightContentRender: () => null,
};
};
import React, { Component } from "react";
import { Resizable } from "react-resizable";
function Resizecell({ onResize, onResizeStop, width, onClick, ...restProps }) {
return (
<Resizable
width={width??1}
height={0}
onResize={onResize}
onResizeStop={onResizeStop}
>
<th {...restProps} />
</Resizable>
);
}
export default Resizecell;
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react/jsx-key */
import React, { useMemo } from 'react';
import { Card, Tabs } from 'antd';
import Mtable from './mtable';
import PremButton from '../PremButton'; //权限判断fn
import { ExportOutlined } from '@ant-design/icons';
import { useMatch } from '@umijs/max';
function isString(obj) {
return obj.constructor === String ? true : false;
}
export default (props) => {
const actionbtn = {
add: <PremButton {...props?.addconfig}>新增</PremButton>,
export: (
<PremButton
{...props?.exportconfig}
btn={{
...props.exportconfig,
icon: <ExportOutlined />,
}}
>
导出
</PremButton>
),
};
//右上角 pageextra 类型 1.reactDom 2.string 以逗号隔开 为none时不显示
const renderextra = () => {
if (props.pageextra && !isString(props.pageextra)) {
if (typeof props?.pageextra == 'function') {
return props.pageextra?.();
} else {
return props.pageextra;
}
} else if (props.pageextra === 'none') {
return <div style={{ height: 28, width: 64 }} />;
} else if (props.pageextra) {
let strarr = props.pageextra ? props.pageextra.split(',') : ['add'];
return strarr.map((it, i) => {
return (
<div key={i} style={{ marginRight: i == strarr.length - 1 ? 0 : 6 }}>
{actionbtn[it]}
</div>
);
});
}
};
const match = useMatch({ path: '/welcome' });
const items = useMemo(() => {
if (props.nocardtab && props.tabList) {
return props.tabList.map((it) => {
return {
label: it.tab,
key: it.key,
children: (
<>
<div style={{ display: 'flex', width: '100%' }}>
{(props.childposition == 'left' || !props.childposition) && props.children}
<div style={{ flex: 1, overflow: 'hidden', paddingLeft: `${props.childposition == 'left' ? '15px' : '0'}` }}>
{props.childposition == 'top' && props.children}
<Mtable {...props} activeTabKey={null} />
</div>
{(props.childposition == 'right' || !props.childposition) && props.children}
</div>
{props.childposition == 'bottom' && props.children}
</>
),
};
});
}
}, [props.tabList]);
return (
<div className="diycard">
{props.withCard === false ? (
<>
<div style={{ display: 'flex', width: '100%' }}>
{(props.childposition == 'left' || !props.childposition) && props.children}
<div style={{ flex: 1, width: '100%' }}>
{props.childposition == 'top' && props.children}
<Mtable {...props} />
</div>
{(props.childposition == 'right' || !props.childposition) && props.children}
</div>
{props.childposition == 'bottom' && props.children}
</>
) : !props.nocardtab ? (
<Card
bordered={props.bordered === false ? false : true}
style={{ height: '100%' }}
title={props.pagetitle}
extra={<div className="center">{renderextra()}</div>}
activeTabKey={props.activeTabKey}
tabList={props.tabList}
onTabChange={props.onTabChange}
>
<div style={{ display: 'flex', width: '100%' }}>
{(props.childposition == 'left' || !props.childposition) && props.children}
<div style={{ flex: 1, overflow: 'hidden', paddingLeft: `${props.childposition == 'left' ? '15px' : '0'}` }}>
{props.childposition == 'top' && props.children}
<Mtable {...props} />
</div>
{(props.childposition == 'right' || !props.childposition) && props.children}
</div>
{props.childposition == 'bottom' && props.children}
</Card>
) : (
<div className="tabsTable">
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '16px 20px 0' }}>
<div style={{ fontSize: 16, fontWeight: 500 }}>{props.pagetitle}</div>
<div className="center">{renderextra()}</div>
</div>
<Tabs items={items} onChange={props.onTabChange} activeTabKey={props.activeTabKey} />
</div>
)}
</div>
);
};
This diff is collapsed.
/* eslint-disable react-hooks/exhaustive-deps */
import { ProDescriptions } from '@ant-design/pro-components';
import React, { useMemo, useState } from 'react';
import { doFetch } from '@/utils/doFetch';
import PropTypes from 'prop-types';
import styles from './index.less';
import { Col } from 'antd';
const { Item } = ProDescriptions;
function DetailPro(props) {
let { fields } = props;
const [curitem, setcuritem] = useState(props.detailData ?? {}); //全部数据
const dataProps = useMemo(() => {
if (props.detailData) {
setcuritem(props.detailData);
return { dataSource: props.detailData };
} else {
return {
request: async () => {
let res = await doFetch({ url: props.detailpath, params: props.params });
setcuritem(res?.data?.data ?? {});
return {
success: res?.code == '0000',
data: res?.data?.data ?? {},
};
},
};
}
}, [props.detailData]);
return (
<ProDescriptions column={3} {...dataProps} title={props.title}>
{fields?.map((it, i) => {
const dataIndexs =
!it?.render || it?.valueType == 'option'
? { dataIndex: it.dataIndex, valueType: it.valueType }
: {};
if (it.valueType == 'split') {
return (
<>
<Col span={24}>
<div className={styles.title} style={{ borderWidth: i == 0 ? 0 : 1 }}>
{it.title}
</div>
</Col>
</>
);
}
return (
<Item
span={it?.span ? it?.span : fields[i + 1]?.valueType == 'split' ? 3 : 1}
key={it.dataIndex}
label={it.title}
{...dataIndexs}
>
{it?.render ? it?.render?.(curitem[it.dataIndex], curitem) : ''}
</Item>
);
})}
</ProDescriptions>
);
}
DetailPro.propTypes = {
detailpath: PropTypes.string,
params: PropTypes.object,
detailData: PropTypes.object,
fields: PropTypes.array,
};
export default DetailPro;
@import '~antd/es/style/variable.less';
.title {
position: relative;
width: 100%;
padding-top: 8px;
padding-left: 12px;
font-weight: bolder;
&::before {
position: absolute;
top: 11px;
left: 0;
width: 3px;
height: 16px;
background-color: @primary-color;
border-radius: 4px;
content: '';
}
&::after {
position: absolute;
top: 18px;
right: 0px;
width: calc(100% - 160px);
height: 1px;
border-bottom: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
content: '';
}
}
import InitForm from '../InitForm';
import { Drawer } from 'antd';
import React, { useState, memo } from 'react';
import DetailPro from '../DetailPro';
import PropTypes from 'prop-types';
function DrawerPro(props) {
let newProps = { ...props };
delete newProps.children;
const detailprops = {
...newProps,
detailpath: props.detailpath,
fields: props.fields,
detailData: props.detailData,
};
return (
<Drawer
maskClosable={false}
placement="right"
closable={true}
getContainer={false}
style={{ position: 'absolute', transform: 'translateX(0)', maxWidth: '100%' }}
width={'100%'}
destroyOnClose={true}
{...props}
>
{props.val == 'only' && props.children}
{props.val == 'only' ? null : props.val == 'detail' ? (
<DetailPro {...detailprops} />
) : (
<InitForm {...newProps} />
)}
</Drawer>
);
}
DrawerPro.propTypes = {
detailpath: PropTypes.string, //详情
params: PropTypes.object, //详情|表单编辑 参数
fields: PropTypes.array, //详情列表
detailData: PropTypes.object, //详情数据
};
export default DrawerPro;
import { GithubOutlined } from '@ant-design/icons';
import { DefaultFooter } from '@ant-design/pro-components';
const Footer = () => {
const defaultMessage = '江苏南高';
const currentYear = new Date().getFullYear();
return (
<DefaultFooter
style={{
background: 'none',
}}
copyright={`${currentYear} ${defaultMessage}`}
links={[
{
key: 'basepro',
title: 'basepro',
href: 'https://pro.ant.design',
blankTarget: true,
},
{
key: 'github',
title: <GithubOutlined />,
href: 'https://github.com/ant-design/ant-design-pro',
blankTarget: true,
},
{
key: 'Ant Design',
title: 'Ant Design',
href: 'https://ant.design',
blankTarget: true,
},
]}
/>
);
};
export default Footer;
@import (reference) '~antd/es/style/themes/index';
.container > * {
background-color: @popover-bg;
border-radius: 4px;
box-shadow: @shadow-1-down;
}
@media screen and (max-width: @screen-xs) {
.container {
width: 100% !important;
}
.container > * {
border-radius: 0 !important;
}
}
import { Dropdown } from 'antd';
import type { DropDownProps } from 'antd/es/dropdown';
import classNames from 'classnames';
import React from 'react';
import styles from './index.less';
export type HeaderDropdownProps = {
overlayClassName?: string;
overlay: React.ReactNode | (() => React.ReactNode) | any;
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
} & Omit<DropDownProps, 'overlay'>;
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => (
<Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
);
export default HeaderDropdown;
@import (reference) '~antd/es/style/themes/index';
.headerSearch {
display: inline-flex;
align-items: center;
.input {
width: 0;
min-width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
transition: width 0.3s, margin-left 0.3s;
:global(.ant-select-selection) {
background: transparent;
}
input {
box-shadow: none !important;
}
&.show {
width: 210px;
margin-left: 8px;
}
}
}
import { SearchOutlined } from '@ant-design/icons';
import type { InputRef } from 'antd';
import { AutoComplete, Input } from 'antd';
import type { AutoCompleteProps } from 'antd/es/auto-complete';
import classNames from 'classnames';
import useMergedState from 'rc-util/es/hooks/useMergedState';
import React, { useRef } from 'react';
import styles from './index.less';
export type HeaderSearchProps = {
onSearch?: (value?: string) => void;
onChange?: (value?: string) => void;
onVisibleChange?: (b: boolean) => void;
className?: string;
placeholder?: string;
options: AutoCompleteProps['options'];
defaultVisible?: boolean;
visible?: boolean;
defaultValue?: string;
value?: string;
};
const HeaderSearch: React.FC<HeaderSearchProps> = (props) => {
const {
className,
defaultValue,
onVisibleChange,
placeholder,
visible,
defaultVisible,
...restProps
} = props;
const inputRef = useRef<InputRef | null>(null);
const [value, setValue] = useMergedState<string | undefined>(defaultValue, {
value: props.value,
onChange: props.onChange,
});
const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, {
value: props.visible,
onChange: onVisibleChange,
});
const inputClass = classNames(styles.input, {
[styles.show]: searchMode,
});
return (
<div
className={classNames(className, styles.headerSearch)}
onClick={() => {
setSearchMode(true);
if (searchMode && inputRef.current) {
inputRef.current.focus();
}
}}
onTransitionEnd={({ propertyName }) => {
if (propertyName === 'width' && !searchMode) {
if (onVisibleChange) {
onVisibleChange(searchMode);
}
}
}}
>
<SearchOutlined
key="Icon"
style={{
cursor: 'pointer',
}}
/>
<AutoComplete
key="AutoComplete"
className={inputClass}
value={value}
options={restProps.options}
onChange={(completeValue) => setValue(completeValue)}
>
<Input
size="small"
ref={inputRef}
defaultValue={defaultValue}
aria-label={placeholder}
placeholder={placeholder}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (restProps.onSearch) {
restProps.onSearch(value);
}
}
}}
onBlur={() => {
setSearchMode(false);
}}
/>
</AutoComplete>
</div>
);
};
export default HeaderSearch;
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useEffect, useRef, useState, memo, useMemo } from 'react';
import { EditableProTable } from '@ant-design/pro-components';
import { Tooltip } from 'antd';
import { doFetch } from '@/utils/doFetch';
const EditTable = (props) => {
const {
actionRef, //表格动作
formRef, //表单Ref
rowKey, // key
columns = [], //columns
style, //style
path, //接口地址
extraparams, //额外参数
pageSize, //修改默认pageSize
pagination, //分页设置
x, //横向滚动
refreshDep, //依赖刷新 (已废弃)
getDefaultSelected, //存在默认选中向上返回选中值
} = props;
const actionRefs = actionRef ?? useRef(),
formRefs = formRef ?? useRef(),
ifspagination = pagination == 'false' || pagination === false,
[size, setsize] = useState('small');
//调用接口
const request = async (params, sort, filter) => {
if (!path) return;
let newparams = {
...params,
...extraparams, //父组件传参
pageIndex: params.current,
pageSize: params.pageSize || pageSize,
};
delete newparams.current;
if (ifspagination) {
delete newparams.pageIndex;
delete newparams.pageSize;
}
const result = await doFetch({ url: path, params: newparams });
//分页结果
let data = result?.data?.page?.list,
success = true,
total = result?.data?.page?.total;
//不带分页获取结果
if (ifspagination || !data) {
data = result?.data?.dataList;
total = result?.data?.dataList?.length;
}
//存在默认选中向上返回选中值
getDefaultSelected && getDefaultSelected(result?.data);
return {
data,
success,
total,
};
};
let columncs = useMemo(() => {
return columns.map((item, index) => {
let it = { ...item };
let itemwidth = it.width ? it.width : 'auto';
let options = {};
if (it.valueType == 'select' || it.valueType == 'checkbox') {
if (Array.isArray(it.options)) {
options = {
fieldProps: {
options: [...it.options],
},
};
} else if (it.options) {
options = {
request: async (params) => {
let list = await doFetch({ url: it?.options?.path, params: it?.options?.params });
return list.data.dataList;
},
};
}
}
if (it.valueType == 'option') {
options = {
key: 'option',
dataIndex: 'option',
fixed: 'right',
};
}
if (!it.render) {
options = {
...options,
render: (text, row) => {
return (
<Tooltip title={row[it.dataIndex]} placement="topLeft">
<span className="table-cell">{row[it.dataIndex] ?? '-'}</span>
</Tooltip>
);
},
};
}
options = {
...options,
width: itemwidth,
};
delete it.formItemProps;
return {
...it,
...options,
};
});
}, [columns]);
return (
<EditableProTable
{...props}
recordCreatorProps={false}
size={size}
onSubmit={(params) => {
console.log(params, 'onSubmit');
}}
onSizeChange={(size) => {
localStorage.setItem('size', size); //设置全局表格规格缓存
setsize(size);
}}
columns={columncs ?? []}
style={style || {}}
actionRef={actionRefs}
formRef={formRefs}
rowKey={rowKey ?? 'id'} //表格每行数据的key
dateFormatter="string"
request={request}
scroll={
x
? {
x: x,
}
: {}
}
pagination={
ifspagination
? false
: {
showTotal: (total, range) => <span>{total}</span>,
showQuickJumper: true,
showSizeChanger: true,
pageSizeOptions: [5, 10, 15, 30, 50, 100, 200],
defaultPageSize: pageSize || 15,
}
}
editable={{
type: 'multiple',
editableKeys: props?.rowSelection?.selectedRowKeys ?? [],
...props?.editable,
}}
search={{
filterType: 'light', //轻量模式
}}
/>
);
};
export default memo(EditTable);
/* eslint-disable react-hooks/exhaustive-deps */
import 'braft-editor/dist/index.css';
import BraftEditor from 'braft-editor';
import React, { useState, useMemo, useEffect } from 'react';
import moment from 'moment';
import defaultSetting from '../../../../config/defaultSettings';
export default function EditorItem({
value,
onChange,
height,
serverURL,
style,
bordered,
formRef,
curkey,
}) {
let UploadFn = (param) => {
const xhr = new XMLHttpRequest();
const fd = new FormData();
const successFn = (response) => {
// 假设服务端直接返回文件上传后的地址
// 上传成功后调用param.success并传入上传后的文件地址
param.success({
url: xhr.responseText ? JSON.parse(xhr.responseText).data?.dataList[0].url : null,
meta: {
id: moment(),
title: param.file.name,
alt: param.file.name,
loop: true, // 指定音视频是否循环播放
autoPlay: true, // 指定音视频是否自动播放
controls: true, // 指定音视频是否显示控制栏
poster: 'http://xxx/xx.png', // 指定视频播放器的封面
},
});
};
const progressFn = (event) => {
// 上传进度发生变化时调用param.progress
param.progress((event.loaded / event.total) * 100);
};
const errorFn = (response) => {
// 上传发生错误时调用param.error
param.error({
msg: '上传失败',
});
};
xhr.upload.addEventListener('progress', progressFn, false);
xhr.addEventListener('load', successFn, false);
xhr.addEventListener('error', errorFn, false);
xhr.addEventListener('abort', errorFn, false);
fd.append('file', param.file);
xhr.open(
'POST',
serverURL
? serverURL
: defaultSetting.proxypath + '/ngic-base-business/sysAttachment/uploadFile',
true,
);
xhr.send(fd);
};
return (
<div
style={{
...style,
border: bordered === false ? '#f9f9f9 solid 1px' : '#ddd solid 1px',
border: '#ddd solid 1px',
height: height ? height : 400,
overflow: 'hidden',
}}
>
<BraftEditor
media={{ uploadFn: UploadFn }}
value={BraftEditor.createEditorState(value)}
onChange={onChange}
/>
</div>
);
}
This diff is collapsed.
import React, { createElement, memo, useRef, useState } from 'react';
import {
ProForm,
ProFormDependency,
ProFormSelect,
ProFormText,
ProFormGroup,
ProFormList,
ProCard,
} from '@ant-design/pro-components';
import moment from 'moment';
import { doFetch } from '@/utils/doFetch';
import styles from './index.less';
import FormItems from './FormItems';
function upperCase(str) {
const newStr = str.slice(0, 1).toUpperCase() + str.slice(1);
return newStr;
}
let FormRender = memo(({ fields = [], colProps, proformRef }) => {
return (
<>
{fields
.filter((it) => it.hideInForm !== true)
.map((item, index) => {
let key = item?.valueType ? upperCase(item?.valueType) : 'Input';
let { hideInForm } = item;
item.formItemProps = item.formItemProps ?? { rules: [] };
if (item.valueType == 'split') {
return (
<div className={styles.title} style={{ borderWidth: index == 0 ? 0 : 1 }}>
{item.title}
</div>
);
}
if (hideInForm && Object.keys(hideInForm)) {
return (
<ProFormDependency name={Object.keys(hideInForm)}>
{(params) => {
let ifs = true;
let res = Object.keys(hideInForm).map((its) => {
if (Array.isArray(hideInForm[its])) {
return !hideInForm[its].includes(params[its]);
} else {
let vals = hideInForm[its].reverse; //取反 即不存在当前数组中的
return vals.indexOf(params[its]) != -1;
}
});
ifs = res.includes(false);
if (ifs) {
return;
} else {
return (
<>
{createElement(FormItems[key], {
item: item,
colProps: colProps,
key: item.dataIndex,
formRef: proformRef,
})}
</>
);
}
}}
</ProFormDependency>
);
} else {
return (
<>
{createElement(FormItems[key], {
item: item,
colProps: colProps,
key: item.dataIndex,
formRef: proformRef,
})}
</>
);
}
})}
</>
);
});
function InitForm({
formRef,
onFinish = (vals) => {
console.log(vals);
},
formKey,
params = {},
detailpath = '',
defaultFormValue = {},
submitter,
fields,
colProps = { xs: 24, sm: 24, md: 12, lg: 12, xl: 12, xxl: 12 },
}) {
let proformRef = useRef();
proformRef = formRef ?? proformRef;
return (
<ProForm
style={{ overflow: 'hidden' }}
formRef={proformRef}
onFinish={onFinish}
formKey={formKey ?? parseInt(Math.random() * 1000000)}
params={params}
submitter={submitter ?? true}
grid={true}
rowProps={{
gutter: 12,
}}
request={async (params) => {
if (detailpath) {
let res = await doFetch({ url: detailpath, params });
return {
...(res?.data?.data ?? {}),
...defaultFormValue,
};
} else {
return {
...defaultFormValue,
};
}
}}
autoFocusFirstInput
>
<FormRender
fields={fields.filter((it) => it.valueType != 'option')}
colProps={colProps}
proformRef={proformRef}
/>
</ProForm>
);
}
export default InitForm;
@import '~antd/es/style/variable.less';
.title {
position: relative;
width: 100%;
margin-bottom: 8px;
padding-left: 16px;
color: #000000;
font-weight: bolder;
font-size: 16px;
&::before {
position: absolute;
top: 4px;
left: 7px;
width: 3px;
height: 16px;
background-color: @primary-color;
border-radius: 4px;
content: '';
}
&::after {
position: absolute;
top: 14px;
right: 0px;
width: calc(100% - 160px);
height: 1px;
border-bottom: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
content: '';
}
}
import { BellOutlined } from '@ant-design/icons';
import { Badge, Spin, Tabs } from 'antd';
import classNames from 'classnames';
import useMergedState from 'rc-util/es/hooks/useMergedState';
import React from 'react';
import HeaderDropdown from '../HeaderDropdown';
import styles from './index.less';
import type { NoticeIconTabProps } from './NoticeList';
import NoticeList from './NoticeList';
const { TabPane } = Tabs;
export type NoticeIconProps = {
count?: number;
bell?: React.ReactNode;
className?: string;
loading?: boolean;
onClear?: (tabName: string, tabKey: string) => void;
onItemClick?: (item: API.NoticeIconItem, tabProps: NoticeIconTabProps) => void;
onViewMore?: (tabProps: NoticeIconTabProps, e: MouseEvent) => void;
onTabChange?: (tabTile: string) => void;
style?: React.CSSProperties;
onPopupVisibleChange?: (visible: boolean) => void;
popupVisible?: boolean;
clearText?: string;
viewMoreText?: string;
clearClose?: boolean;
emptyImage?: string;
children?: React.ReactElement<NoticeIconTabProps>[];
};
const NoticeIcon: React.FC<NoticeIconProps> & {
Tab: typeof NoticeList;
} = (props) => {
const getNotificationBox = (): React.ReactNode => {
const {
children,
loading,
onClear,
onTabChange,
onItemClick,
onViewMore,
clearText,
viewMoreText,
} = props;
if (!children) {
return null;
}
const panes: React.ReactNode[] = [];
React.Children.forEach(children, (child: React.ReactElement<NoticeIconTabProps>): void => {
if (!child) {
return;
}
const { list, title, count, tabKey, showClear, showViewMore } = child.props;
const len = list && list.length ? list.length : 0;
const msgCount = count || count === 0 ? count : len;
const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title;
panes.push(
<TabPane tab={tabTitle} key={tabKey}>
<NoticeList
clearText={clearText}
viewMoreText={viewMoreText}
list={list}
tabKey={tabKey}
onClear={(): void => onClear && onClear(title, tabKey)}
onClick={(item): void => onItemClick && onItemClick(item, child.props)}
onViewMore={(event): void => onViewMore && onViewMore(child.props, event)}
showClear={showClear}
showViewMore={showViewMore}
title={title}
/>
</TabPane>,
);
});
return (
<>
<Spin spinning={loading} delay={300}>
<Tabs className={styles.tabs} onChange={onTabChange}>
{panes}
</Tabs>
</Spin>
</>
);
};
const { className, count, bell } = props;
const [visible, setVisible] = useMergedState<boolean>(false, {
value: props.popupVisible,
onChange: props.onPopupVisibleChange,
});
const noticeButtonClass = classNames(className, styles.noticeButton);
const notificationBox = getNotificationBox();
const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />;
const trigger = (
<span className={classNames(noticeButtonClass, { opened: visible })}>
<Badge count={count} style={{ boxShadow: 'none' }} className={styles.badge}>
{NoticeBellIcon}
</Badge>
</span>
);
if (!notificationBox) {
return trigger;
}
return (
<HeaderDropdown
placement="bottomRight"
overlay={notificationBox}
overlayClassName={styles.popover}
trigger={['click']}
visible={visible}
onVisibleChange={setVisible}
>
{trigger}
</HeaderDropdown>
);
};
NoticeIcon.defaultProps = {
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
};
NoticeIcon.Tab = NoticeList;
export default NoticeIcon;
@import (reference) '~antd/es/style/themes/index';
.list {
max-height: 400px;
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
.item {
padding-right: 24px;
padding-left: 24px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s;
.meta {
width: 100%;
}
.avatar {
margin-top: 4px;
background: @component-background;
}
.iconElement {
font-size: 32px;
}
&.read {
opacity: 0.4;
}
&:last-child {
border-bottom: 0;
}
&:hover {
background: @primary-1;
}
.title {
margin-bottom: 8px;
font-weight: normal;
}
.description {
font-size: 12px;
line-height: @line-height-base;
}
.datetime {
margin-top: 4px;
font-size: 12px;
line-height: @line-height-base;
}
.extra {
float: right;
margin-top: -1.5px;
margin-right: 0;
color: @text-color-secondary;
font-weight: normal;
}
}
.loadMore {
padding: 8px 0;
color: @primary-6;
text-align: center;
cursor: pointer;
&.loadedAll {
color: rgba(0, 0, 0, 0.25);
cursor: unset;
}
}
}
.notFound {
padding: 73px 0 88px;
color: @text-color-secondary;
text-align: center;
img {
display: inline-block;
height: 76px;
margin-bottom: 16px;
}
}
.bottomBar {
height: 46px;
color: @text-color;
line-height: 46px;
text-align: center;
border-top: 1px solid @border-color-split;
border-radius: 0 0 @border-radius-base @border-radius-base;
transition: all 0.3s;
div {
display: inline-block;
width: 50%;
cursor: pointer;
transition: all 0.3s;
user-select: none;
&:only-child {
width: 100%;
}
&:not(:only-child):last-child {
border-left: 1px solid @border-color-split;
}
}
}
import { Avatar, List } from 'antd';
import classNames from 'classnames';
import React from 'react';
import styles from './NoticeList.less';
export type NoticeIconTabProps = {
count?: number;
showClear?: boolean;
showViewMore?: boolean;
style?: React.CSSProperties;
title: string;
tabKey: API.NoticeIconItemType;
onClick?: (item: API.NoticeIconItem) => void;
onClear?: () => void;
emptyText?: string;
clearText?: string;
viewMoreText?: string;
list: API.NoticeIconItem[];
onViewMore?: (e: any) => void;
};
const NoticeList: React.FC<NoticeIconTabProps> = ({
list = [],
onClick,
onClear,
title,
onViewMore,
emptyText,
showClear = true,
clearText,
viewMoreText,
showViewMore = false,
}) => {
if (!list || list.length === 0) {
return (
<div className={styles.notFound}>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
alt="not found"
/>
<div>{emptyText}</div>
</div>
);
}
return (
<div>
<List<API.NoticeIconItem>
className={styles.list}
dataSource={list}
renderItem={(item, i) => {
const itemCls = classNames(styles.item, {
[styles.read]: item.read,
});
// eslint-disable-next-line no-nested-ternary
const leftIcon = item.avatar ? (
typeof item.avatar === 'string' ? (
<Avatar className={styles.avatar} src={item.avatar} />
) : (
<span className={styles.iconElement}>{item.avatar}</span>
)
) : null;
return (
<div
onClick={() => {
onClick?.(item);
}}
>
<List.Item className={itemCls} key={item.key || i}>
<List.Item.Meta
className={styles.meta}
avatar={leftIcon}
title={
<div className={styles.title}>
{item.title}
<div className={styles.extra}>{item.extra}</div>
</div>
}
description={
<div>
<div className={styles.description}>{item.description}</div>
<div className={styles.datetime}>{item.datetime}</div>
</div>
}
/>
</List.Item>
</div>
);
}}
/>
<div className={styles.bottomBar}>
{showClear ? (
<div onClick={onClear}>
{clearText} {title}
</div>
) : null}
{showViewMore ? (
<div
onClick={(e) => {
if (onViewMore) {
onViewMore(e);
}
}}
>
{viewMoreText}
</div>
) : null}
</div>
</div>
);
};
export default NoticeList;
@import (reference) '~antd/es/style/themes/index';
.popover {
position: relative;
width: 336px;
}
.noticeButton {
display: inline-block;
cursor: pointer;
transition: all 0.3s;
}
.icon {
padding: 4px;
vertical-align: middle;
}
.badge {
font-size: 16px;
}
.tabs {
:global {
.ant-tabs-nav-list {
margin: auto;
}
.ant-tabs-nav-scroll {
text-align: center;
}
.ant-tabs-nav {
margin-bottom: 0;
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import React, { Component } from 'react';
function Loading(props) {
return <div>loading....</div>;
}
export default Loading;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment