Commit 232589d0 authored by wuhao's avatar wuhao 🎯

asder

parents
Pipeline #409 failed with stages
# 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
/.vscode
# misc
.DS_Store
npm-debug.log*
yarn-error.log
/shwafer
/coverage
.idea
yarn.lock
package-lock.json
*bak
.vscode
# visual studio code
.history
*.log
functions/*
.temp/**
# umi
.umi
.umi-production
# screenshot
screenshot
.firebase
.eslintcache
build
# 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).
// https://umijs.org/config/
import { defineConfig } from "umi";
export default defineConfig({
plugins: [
// https://github.com/zthxxx/react-dev-inspector
"react-dev-inspector/plugins/umi/react-inspector",
],
// https://github.com/zthxxx/react-dev-inspector#inspector-loader-props
inspectorConfig: {
exclude: [],
babelPlugins: [],
babelOptions: {},
},
});
// https://umijs.org/config/
import { defineConfig } from "umi";
import { join } from "path";
import defaultSettings from "./defaultSettings";
import proxy from "./proxy";
import routes from "./routes";
const { REACT_APP_ENV } = process.env;
export default defineConfig({
hash: true,
antd: {},
dva: {
hmr: true,
},
layout: {
// https://umijs.org/zh-CN/plugins/plugin-layout
locale: true,
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,
},
dynamicImport: {
loading: "@ant-design/pro-layout/es/PageLoading",
},
targets: {
ie: 11,
},
// umi routes: https://umijs.org/docs/routing
routes,
// Theme for antd: https://ant.design/docs/react/customize-theme-cn
theme: {
"root-entry-name": "variable",
},
// esbuild is father build tools
// https://umijs.org/plugins/plugin-esbuild
esbuild: {},
title: false,
ignoreMomentLocale: true,
proxy: proxy[REACT_APP_ENV || "dev"],
history: {
type: "hash",
},
publicPath: "./",
manifest: {
basePath: "/",
},
// Fast Refresh 热更新
fastRefresh: {},
nodeModulesTransform: {
type: "all",
},
//mfsu: {},
//webpack5: {}
});
const Settings = {
navTheme: "dark",
primaryColor: "#1890ff",
layout: "side",
contentWidth: "Fluid",
fixedHeader: true,
fixSiderbar: true,
pwa: false,
logo: "https://img1.baidu.com/it/u=4245241897,3920774166&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
headerHeight: 48,
splitMenus: false,
name: "MES",
};
export default Settings;
/**
* 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
* -------------------------------
* 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
*/
export default {
dev: {
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
"/mes/": {
// 要代理的地址
target: "http://192.168.40.39:18040/", //39:18040 23/mes/ //60 翔
changeOrigin: true,
pathRewrite: {
"^/mes": "",
},
},
"/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: {
"^": "",
},
},
},
};
export default [
{
path: "/user",
layout: false,
routes: [
{
path: "/user",
routes: [
{
name: "登录",
path: "/user/login",
component: "./user/Login",
},
{
name: "注册",
path: "/user/logon",
component: "./user/Logon",
},
],
},
{
component: "./404",
},
],
},
{
path: "/",
redirect: "/welcome",
},
{
path: "/welcome",
name: "首页",
icon: "smile",
component: "./Welcome",
},
{
path: "/system",
name: "系统基础管理",
icon: "setting",
routes: [
{
path: "/system",
redirect: "/system/jiagou",
},
{
path: "/system/staff",
name: "用户管理",
component: "./system/Staff",
},
{
path: "/system/charactor",
name: "角色管理",
component: "./system/Charactor",
},
{
path: "/system/jiagou",
name: "组织管理",
component: "./system/Jiagou",
},
{
path: "/system/parts",
name: "工厂管理",
component: "./system/Parts",
},
{
path: "/system/shop",
name: "车间管理",
component: "./system/Shop",
},
{
path: "/system/productionline",
name: "产线管理",
component: "./system/Productionline",
},
{
path: "/system/section",
name: "工段管理",
component: "./system/Section",
},
{
path: "/system/station",
name: "工位管理",
component: "./system/Station",
},
{
component: "./404",
},
],
},
{
path: "/craft",
name: "工艺设置",
icon: "setting",
routes: [
{
path: "/craft",
redirect: "/craft/process",
},
{
path: "/craft/process",
name: "工序管理",
component: "./craft/Process",
},
{
path: "/craft/gongyi",
name: "工艺路线",
component: "./craft/Gongyi",
},
{
path: "/craft/materiel",
name: "物料管理",
component: "./craft/Materiel",
},
{
path: "/craft/gather",
name: "采集站点",
component: "./craft/Gather",
},
{
component: "./404",
},
],
},
{
path: "/platform",
name: "系统管理",
icon: "desktop",
routes: [
{
path: "/platform",
redirect: "/platform/dic",
},
{
path: "/platform/dic",
name: "数据字典",
component: "./platform/Dic",
},
{
path: "/platform/specialset",
name: "特殊配置",
component: "./platform/Specialset",
},
{
path: "/platform/rule",
name: "编号规则",
component: "./platform/Rule",
},
{
path: "/platform/specialproperties",
name: "特殊属性",
component: "./platform/Specialproperties",
},
{
path: "/platform/labeltemplate",
name: "标签模板",
component: "./platform/Labeltemplate",
},
{
component: "./404",
},
],
},
{
path: "/schedule",
name: "排班设置",
icon: "calendar",
routes: [
{
path: "/schedule",
redirect: "/schedule/shift",
},
{
path: "/schedule/shift",
name: "班次管理",
component: "./schedule/Shift",
},
{
path: "/schedule/shiftgroup",
name: "班组管理",
component: "./schedule/Shiftgroup",
},
{
path: "/schedule/calendar",
name: "工作日历",
component: "./schedule/Calendar",
},
{
component: "./404",
},
],
},
{
path: "/prodplay",
name: "排产管理",
icon: "snippets",
routes: [
{
path: "/prodplay",
redirect: "/prodplay/prodorder",
},
{
path: "/prodplay/prodorder",
name: "生产订单",
component: "./prodplay/Prodorder",
},
{
path: "/prodplay/shopplan",
name: "车间计划",
component: "./prodplay/Shopplan",
},
{
path: "/prodplay/dispatchorder",
name: "派工单",
component: "./prodplay/Dispatchorder",
},
{
component: "./404",
},
],
},
{
path: "/gatherdata",
name: "数据采集",
icon: "usb",
routes: [
{
path: "/gatherdata",
redirect: "/gatherdata/gatherprocess",
},
{
path: "/gatherdata/gatherprocess",
name: "工序采集",
component: "./gatherdata/Gatherprocess",
},
{
component: "./404",
},
],
},
{
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 moment from "moment";
import { parse } from "url"; // mock tableListDataSource
const genList = (current, pageSize) => {
const tableListDataSource = [];
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, res, u) {
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;
let dataSource = [...tableListDataSource].slice(
(current - 1) * pageSize,
current * pageSize
);
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);
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, res, u, b) {
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 = {
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,
};
const getNotices = (req, res) => {
res.json({
data: [
{
id: "000000001",
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
title: "你收到了 14 份新周报",
datetime: "2017-08-09",
type: "notification",
},
{
id: "000000002",
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
title: "你推荐的 曲妮妮 已通过第三轮面试",
datetime: "2017-08-08",
type: "notification",
},
{
id: "000000003",
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
title: "这种模板可以区分多种通知类型",
datetime: "2017-08-07",
read: true,
type: "notification",
},
{
id: "000000004",
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
title: "左侧图标用于区分不同的类型",
datetime: "2017-08-07",
type: "notification",
},
{
id: "000000005",
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
title: "内容不要超过两行字,超出时自动截断",
datetime: "2017-08-07",
type: "notification",
},
{
id: "000000006",
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
title: "曲丽丽 评论了你",
description: "描述信息描述信息描述信息",
datetime: "2017-08-07",
type: "message",
clickClose: true,
},
{
id: "000000007",
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
title: "朱偏右 回复了你",
description: "这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像",
datetime: "2017-08-07",
type: "message",
clickClose: true,
},
{
id: "000000008",
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
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"],
},
},
};
const waitTime = (time = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
async function getFakeCaptcha(req, res) {
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, res) => {
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, res) => {
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, res) => {
access = "";
res.send({
data: {},
success: true,
});
},
"POST /api/register": (req, res) => {
res.send({
status: "ok",
currentAuthority: "user",
success: true,
});
},
"GET /api/500": (req, res) => {
res.status(500).send({
timestamp: 1513932555104,
status: 500,
error: "error",
message: "error",
path: "/base/category/list",
});
},
"GET /api/404": (req, res) => {
res.status(404).send({
timestamp: 1513932643431,
status: 404,
error: "Not Found",
message: "No message available",
path: "/base/category/list/2121212",
});
},
"GET /api/403": (req, res) => {
res.status(403).send({
timestamp: 1513932555104,
status: 403,
error: "Forbidden",
message: "Forbidden",
path: "/base/category/list",
});
},
"GET /api/401": (req, res) => {
res.status(401).send({
timestamp: 1513932555104,
status: 401,
error: "Unauthorized",
message: "Unauthorized",
path: "/base/category/list",
});
},
"GET /api/login/captcha": getFakeCaptcha,
};
{
"name": "MES",
"version": "5.2.0",
"private": true,
"description": "An out-of-box UI solution for enterprise applications",
"scripts": {
"analyze": "cross-env ANALYZE=1 umi build",
"build": "umi 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": "umi g tmp",
"lint": "umi g tmp && npm run lint:js && npm run lint:style && 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 && npm run lint:style",
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
"lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto",
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
"openapi": "umi openapi",
"playwright": "playwright install && playwright test",
"precommit": "lint-staged",
"prettier": "prettier -c --write \"src/**/*\"",
"serve": "umi-serve",
"start": "cross-env UMI_ENV=dev umi dev",
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev umi dev",
"start:no-mock": "cross-env MOCK=none UMI_ENV=dev umi dev",
"start:no-ui": "cross-env UMI_UI=none UMI_ENV=dev umi dev",
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev umi dev",
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev umi dev",
"test": "umi test",
"test:component": "umi test ./src/components",
"test:e2e": "node ./tests/run-tests.js",
"tsc": "tsc --noEmit"
},
"lint-staged": {
"**/*.less": "stylelint --syntax less",
"**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
"**/*.{js,jsx,tsx,ts,less,md,json}": [
"prettier --write"
]
},
"dependencies": {
"@ant-design/compatible": "^1.1.0",
"@ant-design/icons": "^4.7.0",
"@ant-design/pro-card": "^1.18.33",
"@ant-design/pro-descriptions": "^1.10.0",
"@ant-design/pro-form": "^1.52.0",
"@ant-design/pro-layout": "^6.32.0",
"@ant-design/pro-table": "^2.64.1",
"@umijs/route-utils": "^2.0.0",
"ahooks": "^3.3.12",
"antd": "^4.16.13",
"array-move": "^4.0.0",
"braft-editor": "^2.3.9",
"classnames": "^2.3.0",
"crypto-js": "^4.1.1",
"lodash": "^4.17.0",
"moment": "^2.29.1",
"omit.js": "^2.0.2",
"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",
"react-sortable-hoc": "^2.0.0",
"umi": "^3.5.0"
},
"devDependencies": {
"@ant-design/pro-cli": "^2.1.0",
"@playwright/test": "^1.17.0",
"@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.8.0",
"@umijs/openapi": "^1.3.0",
"@umijs/plugin-blocks": "^2.2.0",
"@umijs/plugin-esbuild": "^1.4.0",
"@umijs/plugin-openapi": "^1.3.0",
"@umijs/preset-ant-design-pro": "^1.3.0",
"@umijs/preset-dumi": "^1.1.0",
"@umijs/preset-react": "^1.8.17",
"@umijs/yorkie": "^2.0.5",
"carlo": "^0.9.46",
"cross-env": "^7.0.0",
"cross-port-killer": "^1.3.0",
"detect-installer": "^1.0.0",
"enzyme": "^3.11.0",
"eslint": "^7.32.0",
"express": "^4.17.0",
"gh-pages": "^3.2.0",
"jsdom-global": "^3.0.0",
"lint-staged": "^10.0.0",
"mockjs": "^1.1.0",
"prettier": "^2.5.0",
"puppeteer-core": "^8.0.0",
"stylelint": "^13.0.0",
"swagger-ui-react": "^3.52.0",
"typescript": "^4.5.0",
"umi-serve": "^1.9.10"
},
"engines": {
"node": ">=10.0.0"
},
"gitHooks": {
"commit-msg": "fabric verify-commit"
}
}
// playwright.config.ts
import { devices } from "@playwright/test";
const config = {
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 { SettingDrawer } from "@ant-design/pro-layout";
import { PageLoading } from "@ant-design/pro-layout";
import { history, Link } from "umi";
import RightContent from "@/components/RightContent";
import TagView from "@/components/TagView";
import SiderMenu from "@/components/SiderMenu";
import { queryCurrentUser, getMenu } from "./services/login";
import defaultSettings from "../config/defaultSettings";
import { message } from "antd";
const loginPath = "/user/login";
/** 获取用户信息比较慢的时候会展示一个 loading */
export const initialStateConfig = {
loading: <PageLoading />,
};
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
export async function getInitialState() {
let token = localStorage.getItem("TOKEN_MES");
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,
};
}
return {
fetchUserInfo,
settings: defaultSettings,
getmenuData,
};
} // ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout = async (props) => {
let { initialState, setInitialState } = props;
let token = localStorage.getItem("TOKEN_MES");
return {
rightContentRender: () => <RightContent />,
disableContentMargin: false,
waterMarkProps: {
content: initialState?.currentUser?.name,
},
onPageChange: () => {
const { location } = history; // 如果没有登录,重定向到 login
if (location.pathname != "/user/logon") {
if (
(!initialState?.currentUser && location.pathname !== loginPath) ||
!token
) {
setInitialState((s) => {
return { ...s, currentUser: undefined, newMenu: [] };
});
history.replace(loginPath);
}
}
if (location.pathname == loginPath) {
setInitialState((s) => {
return { ...s, currentUser: undefined, newMenu: [] };
});
}
},
contentStyle: {
padding: "42px 0px 0px 6px",
margin: 0,
},
//接口获取菜单数据
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;
},
locale: false,
},
menuRender: (props, defaultDom) => {
if (props.isMobile) {
return defaultDom;
} else {
return <SiderMenu {...props}></SiderMenu>;
}
},
// 自定义 403 页面
// unAccessible: <div>unAccessible</div>,
// 增加一个 loading 的状态
childrenRender: (children, props) => {
// if (initialState?.loading) return <PageLoading />;
return (
<>
{initialState?.currentUser && location.pathname !== loginPath ? (
<TagView home="/welcome">{children}</TagView>
) : (
children
)}
{!props.location?.pathname?.includes("/login") && (
<SettingDrawer
enableDarkTheme
settings={initialState?.settings}
onSettingChange={(settings) => {
setInitialState((preInitialState) => ({
...preInitialState,
settings,
}));
}}
/>
)}
</>
);
},
...initialState?.settings,
};
};
This diff is collapsed.
import {
HomeOutlined,
SettingFilled,
SmileOutlined,
SyncOutlined,
LoadingOutlined,
} from "@ant-design/icons";
import { Empty } from "antd";
export default ({ isLoading, isEmpty }) => (
<div style={{ padding: "4px 0 0 0", textAlign: "center" }}>
{isEmpty ? (
<div className="center">
<Empty description="没有匹配的数据" style={{ paddingTop: 60 }}></Empty>
</div>
) : isLoading ? (
<div className="center">
<LoadingOutlined style={{ marginRight: 12 }}></LoadingOutlined>{" "}
拼命加载中...
</div>
) : (
"加载完成"
)}
</div>
);
import React, { Component } from "react";
import { Resizable } from "react-resizable";
function Resizecell({ onResize, onResizeStop, width, onClick, ...restProps }) {
return (
<Resizable
width={width}
height={0}
onResize={onResize}
onResizeStop={onResizeStop}
>
<th {...restProps} />
</Resizable>
);
}
export default Resizecell;
import { Row, Col, Card, Grid } from "antd";
import Mcard from "./mcard";
import Mtable from "./mtable";
const { useBreakpoint } = Grid;
export default (props) => {
const screens = useBreakpoint();
return (
<div className="diycard">
{screens.xs ? (
<div
style={{ display: "flex", flexDirection: "column", height: "100%" }}
>
{props.children && (
<div style={{ marginBottom: 12 }}>{props.children}</div>
)}
<div>
<Mcard {...props} />
</div>
</div>
) : props.withCard === false ? (
<div style={{ display: "flex", width: "100%" }}>
<div style={{ flex: 1, width: "100%" }}>
{props.childposition == "top" && props.children}
<Mtable {...props} />
</div>
{(props.childposition == "right" || !props.childposition) &&
props.children}
</div>
) : (
<Card
bordered={props.bordered === false ? false : true}
style={{ height: "100%" }}
title={props.pagetitle || null}
extra={props.pageextra || null}
activeTabKey={props.activeTabKey}
tabList={props.tabList}
onTabChange={props.onTabChange}
>
<div style={{ display: "flex", width: "100%" }}>
<div style={{ flex: 1, width: "100%" }}>
{props.childposition == "top" && props.children}
<Mtable {...props} />
</div>
{(props.childposition == "right" || !props.childposition) &&
props.children}
</div>
</Card>
)}
</div>
);
};
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
.items {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 6px;
cursor: pointer;
> i {
flex: 1;
overflow: hidden;
font-style: normal;
white-space: nowrap;
text-overflow: ellipsis;
}
> span {
width: 25px;
}
&:hover {
background-color: #f9f9f9;
}
}
import React from "react";
import { Col, Tooltip } from "antd";
const Coltext = ({ label, value, col, id }) => {
const getCol = () => {
return col ? col : { xs: 24, sm: 24, md: 24, lg: 8, xl: 8, xxl: 8 };
};
return (
<Col {...getCol()} key={id || "id"}>
<div style={{ display: "flex", curitem: "center" }}>
<div style={{ minWidth: 50, flexShrink: 0 }}>{label}</div>
<Tooltip title={value}>
<div
style={{
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
>
{value || "-"}
</div>
</Tooltip>
</div>
</Col>
);
};
export default Coltext;
import React, { useState, useEffect } from "react";
import {
Table,
Divider,
Card,
Descriptions,
Typography,
Spin,
Row,
Col,
Tooltip,
Rate,
Drawer,
Image,
} from "antd";
import { postFetch } from "@/utils/doFetch";
import AutoTable from "@/components/Tableform";
import styles from "./index.less";
const { Paragraph } = Typography;
const Details = (props) => {
/**
* dataSource: 整个页面的数据,Object
* totalPath: 整个详情页的接口,String
* totalParams: 整个详情页接口的参数
* totalCard: 整个页面的模块,Array
* col: 列数
*
* totalCard中的每一项变量:cardTitle每一个模块的标题;itemData每一个模块的数据信息(Array)
* itemData中的每一项变量:key所对应的字段;title每一项label标题;type每一项的类型(file,img,textarea,table,不填默认就是普通的展示类型);col每一项的占比;columns表格类型所用;path当类型为表格时,若该表格是单独查询出来的可以填入path;urlName 当传入文件为url为字符串时
*
*/
const {
totalPath = "",
dataSource,
totalCard,
totalParams,
col,
bottomNode,
topNode,
} = props;
const [pageData, cp] = useState({});
useEffect(() => {
if (dataSource) {
cp(dataSource);
} else if (!dataSource && !Array.isArray(totalPath)) {
postFetch({
url: totalPath,
params: totalParams ? { ...totalParams } : {},
}).then((res) => {
if (res.code == "0000") {
if (!res.data) {
return;
}
cp(res?.data?.data);
}
});
}
}, []);
const getCol = (itemcol) => {
if (itemcol) {
return itemcol;
} else {
return col ? col : { xs: 24, sm: 24, md: 12, lg: 8, xl: 8, xxl: 6 };
}
};
//获取每一项
const getItem = (itemData) => {
const {
type,
key,
title,
columns,
path,
urlName,
col,
rowKey,
expandable,
} = itemData,
value = dataSource[key];
if (!type || type == "input") {
return value == 0 || value ? (
<Tooltip title={`${title}:${value}`}>
<pre className={col ? "" : styles.one}>{value}</pre>
</Tooltip>
) : (
<div>-</div>
);
} else if (type == "file") {
return (
<div style={{ display: "flex", flexFlow: "row wrap", width: "100%" }}>
{value && typeof value == "string" ? (
<a
href={value}
download={value}
style={{ margin: "0 10px 0 0", display: "block" }}
>
{urlName ? urlName : "下载"}
</a>
) : value && Array.isArray(value) && value?.length ? (
value?.map((el) => {
return (
<a
key={el.url}
href={el.url}
download={el.name}
style={{ margin: "0 10px 0 0", display: "block" }}
>
{el.name}
</a>
);
})
) : (
"-"
)}
</div>
);
} else if (type == "img") {
return (
<div style={{ display: "flex", flexFlow: "row wrap" }}>
{value && value?.length
? value?.map((el) => {
return (
<Image
style={{ margin: "0 10px 0 0" }}
src={el?.url}
width={30}
height={30}
key={el?.uid}
></Image>
);
})
: "-"}
</div>
);
} else if (type == "table") {
return (
<AutoTable
columns={columns || []}
path={path ? path : null}
bordered={false}
dataSource={path ? null : value}
rowKey={rowKey ?? "id"}
expandable={expandable ?? null}
></AutoTable>
);
} else if (type == "rate") {
return (
<Rate
bordered={false}
className={"simple"}
style={{ width: "100%" }}
disabled={itemData.disabled}
allowHalf={itemData.allowHalf ? itemData.allowHalf : false}
value={value || 0}
/>
);
}
};
return (
<Drawer
maskClosable={false}
placement="right"
closable={true}
getContainer={false}
style={{ position: "absolute" }}
{...props}
>
<div className={`${styles.bodyBox} detailCards`}>
<Spin spinning={false}>
{topNode && <div>{topNode}</div>}
{totalCard?.map((item, i) => {
return (
<Card
key={i}
title={item.cardTitle}
bordered={false}
style={{ marginBottom: 16 }}
bodyStyle={{ padding: "16px 10px" }}
>
<Row gutter={[12, 12]}>
{item?.itemData?.map((it, j) => {
return (
<Col key={j} {...getCol(it.col)}>
<div style={{ width: "100%", display: "flex" }}>
<div
style={{
flexShrink: 0,
display: `${it.title ? "block" : "none"}`,
textAlign: "right",
width: 126,
}}
>
{it.title}
</div>
{getItem(it)}
</div>
</Col>
);
})}
</Row>
</Card>
);
})}
{bottomNode && <div>{bottomNode}</div>}
</Spin>
</div>
</Drawer>
);
};
export default Details;
.bodyBox {
width: 100%;
height: 100%;
padding: 16px;
padding-bottom: 0;
overflow-y: auto;
background: #f2f4f5;
}
.one {
overflow: hidden !important;
white-space: nowrap !important;
text-overflow: ellipsis !important;
}
import { Image } from "antd";
import React from "react";
let Images = (props) => {
return <Image {...props} />;
};
export default Images;
This diff is collapsed.
import InitForm from "../InitForm";
import { Drawer } from "antd";
import React, { useState, memo } from "react";
// title="Basic Drawer"
// placement="right"
// closable={false}
// onClose={this.onClose}
// visible={this.state.visible}
// getContainer={false}
// style={{ position: 'absolute' }}
function DrawInitForm(props) {
let newProps = { ...props };
delete newProps.children;
return (
<Drawer
maskClosable={false}
placement="right"
closable={true}
getContainer={false}
style={{ position: "absolute" }}
{...props}
>
{props.children}
<InitForm {...newProps}></InitForm>
</Drawer>
);
}
export default DrawInitForm;
import React, { useEffect } from "react";
import { EditableProTable } from "@ant-design/pro-table";
import ProField from "@ant-design/pro-field";
import ProCard from "@ant-design/pro-card";
import { Button } from "antd";
const EditTable = ({
columns,
value,
onChange,
rowKey,
recordCreatorProps,
maxLength,
}) => {
return (
<EditableProTable
columns={columns}
rowKey={rowKey}
value={value || []}
onChange={onChange}
recordCreatorProps={
recordCreatorProps == "false"
? false
: {
newRecordType: "dataSource",
record: () => ({
[rowKey]: Date.now(),
}),
}
}
editable={{
type: "multiple",
editableKeys: value ? value.map((item) => item[rowKey]) : [],
actionRender: (row, config, defaultDoms) => {
return [defaultDoms.delete];
},
onValuesChange: (record, recordList) => {
onChange(recordList);
},
}}
maxLength={maxLength ?? 1000}
/>
);
};
export default EditTable;
.items {
display: flex;
align-items: center;
justify-content: space-between;
width: "100%";
padding: 8px 6px;
cursor: pointer;
> i {
flex: 1;
overflow: hidden;
font-style: normal;
white-space: nowrap;
text-overflow: ellipsis;
}
> span {
width: 25px;
}
&:hover {
background-color: #f9f9f9;
}
}
import "braft-editor/dist/index.css";
import BraftEditor from "braft-editor";
import React, { useState, useMemo, useEffect } from "react";
import moment from "moment";
export default function Editor({
value,
onChange,
height,
rerender,
serverURL,
style,
bordered,
}) {
let [editorState, ceditor] = useState(
BraftEditor.createEditorState(
'<p style="text-align:start;" size="0" _root="undefined" __ownerID="undefined" __hash="undefined" __altered="false"></p>'
)
),
[defaultvalue, cd] = useState(true);
let handleChange = (editorState) => {
ceditor(editorState);
onChange(editorState.toHTML());
};
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 : window?.dataconfig?.serverURL,
true
);
xhr.send(fd);
};
useEffect(() => {
if (rerender) {
cd(rerender);
}
}, [rerender]);
useEffect(() => {
if (value && defaultvalue) {
ceditor(BraftEditor.createEditorState(value));
cd(false);
}
}, [value]);
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={editorState}
onChange={handleChange}
/>
</div>
);
}
import { useIntl } from "umi";
import { GithubOutlined } from "@ant-design/icons";
import { DefaultFooter } from "@ant-design/pro-layout";
const Footer = () => {
const intl = useIntl();
const defaultMessage = intl.formatMessage({
id: "app.copyright.produced",
defaultMessage: "蚂蚁集团体验技术部出品",
});
const currentYear = new Date().getFullYear();
return (
<DefaultFooter
copyright={`${currentYear} ${defaultMessage}`}
links={[
{
key: "Ant Design Pro",
title: "Ant Design Pro",
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 { Dropdown } from "antd";
import React from "react";
import classNames from "classnames";
import styles from "./index.less";
const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => (
<Dropdown
overlayClassName={classNames(styles.container, cls)}
zIndex={9999999}
placement="bottomRight"
{...restProps}
/>
);
export default HeaderDropdown;
@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 { SearchOutlined } from "@ant-design/icons";
import { AutoComplete, Input } from "antd";
import useMergedState from "rc-util/es/hooks/useMergedState";
import React, { useRef } from "react";
import classNames from "classnames";
import styles from "./index.less";
const HeaderSearch = (props) => {
const {
className,
defaultValue,
onVisibleChange,
placeholder,
visible,
defaultVisible,
...restProps
} = props;
const inputRef = useRef(null);
const [value, setValue] = useMergedState(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={setValue}
>
<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;
@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;
}
}
}
This diff is collapsed.
export default function mockfile(datalist) {
let newdatalist = datalist.map((item, i) => {
return {
uid: item.uid ? item.uid : i + 1,
name: item.name ? item.name : `文件${i + 1}`,
url: item.url ? item.url : item,
};
});
return {
fileList: newdatalist,
};
}
import { BellOutlined } from "@ant-design/icons";
import { Badge, Spin, Tabs } from "antd";
import useMergedState from "rc-util/es/hooks/useMergedState";
import React from "react";
import classNames from "classnames";
import NoticeList from "./NoticeList";
import HeaderDropdown from "../HeaderDropdown";
import styles from "./index.less";
const { TabPane } = Tabs;
const NoticeIcon = (props) => {
const getNotificationBox = () => {
const {
children,
loading,
onClear,
onTabChange,
onItemClick,
onViewMore,
clearText,
viewMoreText,
} = props;
if (!children) {
return null;
}
const panes = [];
React.Children.forEach(children, (child) => {
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 = msgCount > 0 ? `${title} (${msgCount})` : title;
panes.push(
<TabPane tab={tabTitle} key={tabKey}>
<NoticeList
clearText={clearText}
viewMoreText={viewMoreText}
list={list}
tabKey={tabKey}
onClear={() => onClear && onClear(title, tabKey)}
onClick={(item) => onItemClick && onItemClick(item, child.props)}
onViewMore={(event) => 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(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 { Avatar, List } from "antd";
import React from "react";
import classNames from "classnames";
import styles from "./NoticeList.less";
const NoticeList = ({
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
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 (
<List.Item
className={itemCls}
key={item.key || i}
onClick={() => {
onClick?.(item);
}}
>
<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 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";
.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 { useEffect, useState } from "react";
import { Tag, message } from "antd";
import { groupBy } from "lodash";
import moment from "moment";
import { useModel, useRequest } from "umi";
import { getNotices } from "@/services/ant-design-pro/api";
import NoticeIcon from "./NoticeIcon";
import styles from "./index.less";
const getNoticeData = (notices) => {
if (!notices || notices.length === 0 || !Array.isArray(notices)) {
return {};
}
const newNotices = notices.map((notice) => {
const newNotice = { ...notice };
if (newNotice.datetime) {
newNotice.datetime = moment(notice.datetime).fromNow();
}
if (newNotice.id) {
newNotice.key = newNotice.id;
}
if (newNotice.extra && newNotice.status) {
const color = {
todo: "",
processing: "blue",
urgent: "red",
doing: "gold",
}[newNotice.status];
newNotice.extra = (
<Tag
color={color}
style={{
marginRight: 0,
}}
>
{newNotice.extra}
</Tag>
);
}
return newNotice;
});
return groupBy(newNotices, "type");
};
const getUnreadData = (noticeData) => {
const unreadMsg = {};
Object.keys(noticeData).forEach((key) => {
const value = noticeData[key];
if (!unreadMsg[key]) {
unreadMsg[key] = 0;
}
if (Array.isArray(value)) {
unreadMsg[key] = value.filter((item) => !item.read).length;
}
});
return unreadMsg;
};
const NoticeIconView = () => {
const { initialState } = useModel("@@initialState");
const { currentUser } = initialState || {};
const [notices, setNotices] = useState([]);
const { data } = useRequest(getNotices);
useEffect(() => {
setNotices(data || []);
}, [data]);
const noticeData = getNoticeData(notices);
const unreadMsg = getUnreadData(noticeData || {});
const changeReadState = (id) => {
setNotices(
notices.map((item) => {
const notice = { ...item };
if (notice.id === id) {
notice.read = true;
}
return notice;
})
);
};
const clearReadState = (title, key) => {
setNotices(
notices.map((item) => {
const notice = { ...item };
if (notice.type === key) {
notice.read = true;
}
return notice;
})
);
message.success(`${"清空了"} ${title}`);
};
return (
<NoticeIcon
className={styles.action}
count={currentUser && currentUser.unreadCount}
onItemClick={(item) => {
changeReadState(item.id);
}}
onClear={(title, key) => clearReadState(title, key)}
loading={false}
clearText="清空"
viewMoreText="查看更多"
onViewMore={() => message.info("Click on view more")}
clearClose
>
<NoticeIcon.Tab
tabKey="notification"
count={unreadMsg.notification}
list={noticeData.notification}
title="通知"
emptyText="你已查看所有通知"
showViewMore
/>
<NoticeIcon.Tab
tabKey="message"
count={unreadMsg.message}
list={noticeData.message}
title="消息"
emptyText="您已读完所有消息"
showViewMore
/>
<NoticeIcon.Tab
tabKey="event"
title="待办"
emptyText="你已完成所有待办"
count={unreadMsg.event}
list={noticeData.event}
showViewMore
/>
</NoticeIcon>
);
};
export default NoticeIconView;
@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;
}
}
}
import React, { memo } from "react";
import { DatePicker, Radio, Input, InputNumber, Select } from "antd";
import moment from "moment";
import Login from "@/pages/user/Login";
const { RangePicker } = DatePicker;
const { Option } = Select;
const RenderItemType = memo(
(props) => {
const { value, onChange, type, min, max, radioarr, disabled, selectarr } =
props;
if (type == "dateRange") {
return (
<RangePicker
{...props}
value={value ? moment(value) : undefined}
onChange={(val) => {
onChange(val ? moment(val).format("YYYY-MM-DD") : "");
}}
format="YYYY-MM-DD"
style={{ width: "100%" }}
/>
);
} else if (type == "month") {
return (
<DatePicker
{...props}
value={value ? moment(value) : undefined}
onChange={(val) => {
onChange(val ? moment(val).format("YYYY-MM") : "");
}}
picker="month"
format="YYYY-MM"
style={{ width: "100%" }}
/>
);
} else if (type == "year") {
return (
<DatePicker
{...props}
value={value ? moment(value) : undefined}
onChange={(val) => {
onChange(val ? moment(val).format("YYYY") : "");
}}
picker="year"
format="YYYY"
style={{ width: "100%" }}
/>
);
} else if (type == "dateHour") {
return (
<DatePicker
{...props}
value={value ? moment(value) : undefined}
onChange={(val) => {
onChange(val ? moment(val).format("YYYY-MM-DD HH") : "");
}}
format="YYYY-MM-DD HH"
showTime={{ format: "HH" }}
style={{ width: "100%" }}
/>
);
} else if (type == "radio") {
return (
<Radio.Group
{...props}
onChange={(e) => {
onChange(e.target.value);
}}
value={value}
style={{ width: "100%" }}
>
{radioarr.map((el) => {
return (
<Radio key={el.value} value={el.value}>
{el.label}
</Radio>
);
})}
</Radio.Group>
);
} else if (type == "select") {
return (
<Select
{...props}
onChange={(value) => {
onChange(value);
}}
value={value}
style={{ width: "100%" }}
allowClear={true}
>
{selectarr.map((el) => {
return (
<Option key={el.value} value={el.value}>
{el.label}
</Option>
);
})}
</Select>
);
} else if (type == "input") {
return (
<Input
{...props}
value={value}
onChange={(e) => {
onChange(e.target.value);
}}
style={{ width: "100%" }}
allowClear={true}
/>
);
} else if (type == "inputnumber") {
return (
<InputNumber
{...props}
value={value}
onChange={(val) => {
onChange(val);
}}
style={{ width: "100%" }}
allowClear={true}
min={min || null}
max={max || null}
/>
);
}
},
(prev, next) => {
if (prev.rowKey && next.rowKey) {
if (prev.rowKey == next.rowKey) {
return true;
} else {
return false;
}
} else {
if (prev.id == next.id) {
return true;
} else {
return false;
}
}
}
);
export default RenderItemType;
This diff is collapsed.
import { Space } from "antd";
import { QuestionCircleOutlined } from "@ant-design/icons";
import React from "react";
import { useModel, SelectLang } from "umi";
import Avatar from "./AvatarDropdown";
import HeaderSearch from "../HeaderSearch";
import styles from "./index.less";
const GlobalHeaderRight = () => {
const { initialState } = useModel("@@initialState");
if (!initialState || !initialState.settings) {
return null;
}
const { navTheme, layout } = initialState.settings;
let className = styles.right;
if ((navTheme === "dark" && layout === "top") || layout === "mix") {
className = `${styles.right} ${styles.dark}`;
}
return (
<Space className={className}>
<Avatar />
</Space>
);
};
export default GlobalHeaderRight;
This diff is collapsed.
This diff is collapsed.
import React from "react";
import SiderMenu from "./SiderMenu";
const SiderMenuWrapper = React.memo((props) => {
return <SiderMenu {...props} />;
});
export default SiderMenuWrapper;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import {
HomeOutlined,
SettingFilled,
SmileOutlined,
SyncOutlined,
LoadingOutlined,
} from "@ant-design/icons";
import { Empty } from "antd";
export default ({ isLoading, isEmpty }) => (
<div style={{ padding: "4px 0 0 0", textAlign: "center" }}>
{isEmpty ? (
<div className="center">
<Empty description="没有匹配的数据" style={{ paddingTop: 60 }}></Empty>
</div>
) : isLoading ? (
<div className="center">
<LoadingOutlined style={{ marginRight: 12 }}></LoadingOutlined>{" "}
拼命加载中...
</div>
) : (
"加载完成"
)}
</div>
);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
.tag_view {
.tags_container {
position: absolute;
top: 0px;
left: 0px;
z-index: 20;
width: 100%;
height: 36px;
}
}
.child {
position: relative;
height: calc(100vh - 98px);
overflow: hidden;
.contianbox {
height: 100%;
overflow: auto;
overflow-x: hidden;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
export default {
"component.tagSelect.expand": "বিস্তৃত",
"component.tagSelect.collapse": "সঙ্কুচিত",
"component.tagSelect.all": "সব",
};
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
export default {
"app.pwa.offline": "আপনি এখন অফলাইন",
"app.pwa.serviceworker.updated": "নতুন সামগ্রী উপলব্ধ",
"app.pwa.serviceworker.updated.hint":
'বর্তমান পৃষ্ঠাটি পুনরায় লোড করতে দয়া করে "রিফ্রেশ" বোতাম টিপুন',
"app.pwa.serviceworker.updated.ok": "রিফ্রেশ",
};
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.
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.
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.
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.
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.
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